자바의 가비지 컬렉션은 고급 주제 중 하나입니다. 자바 GC 지식은 응용 프로그램 런타임 성능을 세밀하게 조정하는 데 도움이 됩니다.
자바의 가비지 컬렉션
- 자바에서 프로그래머는 사용하지 않는 객체를 파괴하는 데 신경 쓸 필요가 없습니다. 가비지 컬렉터가 이를 처리합니다.
- 가비지 컬렉터는 백그라운드에서 실행되는 데몬 스레드입니다. 기본적으로, 도달할 수 없는 객체를 파괴하여 힙 메모리를 해제합니다.
- 도달할 수 없는 객체는 프로그램의 어떤 부분에서도 더 이상 참조되지 않는 객체입니다.
- 우리는 자바 프로그램에 대해 가비지 컬렉터를 선택할 수 있으며, 이 튜토리얼의 이후 섹션에서 이에 대해 알아볼 것입니다.
자동 가비지 컬렉션이 작동하는 방식은 어떻게 될까요?
자동 가비지 수집은 힙 메모리를 살펴보고 도달할 수 없는 객체를 식별(마킹이라고도 함)하고 그것들을 압축하여 파괴하는 과정입니다. 이 방식의 문제는 객체 수가 증가함에 따라 가비지 수집 시간이 계속 증가한다는 것입니다. 왜냐하면 가비지 수집기는 도달할 수 없는 객체를 찾기 위해 전체 객체 목록을 훑어야 하기 때문입니다. 그러나 응용 프로그램의 경험적 분석에 따르면 대부분의 객체는 짧은 수명을 가지고 있습니다. 이 동작은 JVM의 성능을 향상시키기 위해 사용되었으며 채택된 방법론은 일반적으로 세대별 가비지 수집이라고 합니다. 이 방법에서 힙 공간은 Young Generation, Old or Tenured Generation 및 Permanent Generation과 같은 세대로 분할됩니다. Young generation 힙 공간은 모든 새로운 객체가 생성되는 곳입니다. 한 번 가득 차면, 소량의 가비지 수집(소량 GC로도 알려짐)이 발생합니다. 즉, 이 세대의 모든 죽은 객체가 파괴됩니다. 이 과정은 빠릅니다. 왜냐하면 그래프에서 볼 수 있듯이 대부분의 객체가 죽어 있기 때문입니다. Young generation에서 생존한 객체는 나이가 들어서 결국 오래된 세대로 이동합니다. Old Generation은 오래 살아있는 객체를 저장하는 데 사용됩니다. 일반적으로 Young generation 객체에 대한 임계값이 설정되고 그 나이가 충족되면 객체가 오래된 세대로 이동됩니다. 결국 오래된 세대를 수집해야 합니다. 이 이벤트는 주요 GC(주요 가비지 수집)라고 합니다. 종종 모든 활성 객체가 포함되므로 이것은 훨씬 느립니다. 또한 Young 및 오래된 세대 공간을 모두 청소하는 Full GC가 있습니다. 마지막으로 Java 7까지 Permanent Generation(또는 Perm Gen)이 있었는데, 이는 응용 프로그램에서 사용되는 클래스 및 메서드를 설명하는 데 JVM에서 필요한 메타데이터를 포함했습니다. Java 8에서 제거되었습니다.
자바 가비지 컬렉터
JVM은 실제로 네 가지 다른 가비지 컬렉터를 제공합니다. 각각의 장단점이 있습니다. 어떤 가비지 컬렉터를 사용할지 선택은 우리에게 달려 있으며 처리량과 응용 프로그램 일시 중단 사이에는 극적인 차이가 있을 수 있습니다. 이 모든 것들은 관리되는 힙을 다른 세그먼트로 분할하여 힙의 대부분의 객체가 단명하고 빠르게 재활용되어야 한다는 오래된 가정을 사용합니다. 따라서 네 가지 유형의 가비지 컬렉터는 다음과 같습니다:
Serial GC
이것은 가장 간단한 가비지 컬렉터로, 단일 스레드 시스템과 작은 힙 크기를 위해 설계되었습니다. 작동 중에 모든 응용 프로그램을 멈춥니다. -XX:+UseSerialGC
JVM 옵션을 사용하여 활성화할 수 있습니다.
Parallel/Throughput GC
이것은 JDK 8에서 JVM의 기본 수집기입니다. 이름에서 알 수 있듯이, 이 수집기는 여러 스레드를 사용하여 힙 공간을 스캔하고 압축을 수행합니다. 이 수집기의 단점은 애플리케이션 스레드가 마이너 또는 풀 GC를 수행하는 동안 일시적으로 일시 정지된다는 것입니다. 이 수집기는 그러한 일시 정지를 처리할 수 있는 애플리케이션에 가장 적합하며, 수집기로 인한 CPU 오버헤드를 최적화하려고 할 때 가장 적합합니다.
CMS 수집기
CMS 수집기(“concurrent-mark-sweep”) 알고리즘은 여러 스레드(“concurrent”)를 사용하여 힙(“mark”)을 스캔하여 재활용할 수 있는 사용되지 않는 객체(“sweep”)를 찾습니다. 이 수집기는 두 가지 경우에 Stop-The-World(STW) 모드로 전환됩니다: – 루트의 초기 마킹을 초기화하는 동안, 즉 스레드 진입 지점 또는 정적 변수에서 접근 가능한 오래된 세대의 객체 – 알고리즘이 동시에 실행되는 동안 애플리케이션이 힙의 상태를 변경하고 알고리즘에게 다시 돌아가서 올바른 객체가 마킹되었는지 확인해야 할 경우 이 수집기는 프로모션 실패에 직면할 수 있습니다. 어떤 객체가 젊은 세대에서 오래된 세대로 이동해야 하는데, 수집기가 오래된 세대 공간에 충분한 시간을 확보하지 못한 경우 프로모션 실패가 발생합니다. 이를 방지하기 위해 오래된 세대에 더 많은 힙 크기를 제공하거나 수집기에 더 많은 백그라운드 스레드를 제공할 수 있습니다.
G1 수집기
마지막으로 소개할 것은 4GB 이상의 힙 크기를 대상으로 설계된 Garbage-First 수집기입니다. 힙 크기를 기반으로 1MB에서 32MB에 이르는 영역으로 나눕니다. 힙 전체에 대한 객체의 생존 여부를 결정하기 위해 동시 전역 표시 단계가 있습니다. 표시 단계가 완료되면 G1은 대부분 비어 있는 영역을 알게 됩니다. 이러한 영역에 대해 먼저 접근할 수 없는 객체를 수집하여 일반적으로 많은 무료 공간을 얻습니다. 따라서 G1은 이러한 영역(쓰레기를 포함한)을 먼저 수집하므로 Garbage-First라는 이름이 붙었습니다. G1은 또한 사용자가 정의한 일시 중지 시간 목표를 충족시키기 위한 일시 중지 예측 모델을 사용합니다. 지정된 일시 중지 시간 목표를 기반으로 수집할 영역의 수를 선택합니다. G1 가비지 수집 주기는 다음과 같은 단계를 포함합니다.
-
Young-only 단계: 이 단계에는 젊은 세대 객체만 포함되며 이를 오래된 세대로 승격시킵니다. 젊은 세대 전용 단계와 공간 회수 단계 사이의 전환은 오래된 세대가 특정 임계값, 즉 초기 힙 점유 임계값에 이르도록 차지될 때 시작됩니다. 이때 G1은 일반적인 젊은 세대 전용 수집 대신 초기 표시 젊은 세대 전용 수집을 예약합니다.
-
초기 표시: 이 유형의 수집은 일반적인 어린 세대 전용 수집에 추가로 표시 과정을 시작합니다. 동시 표시는 공간 회수 단계를 위해 유지되어야 하는 모든 현재 활성 객체를 고령 세대 영역에서 결정합니다. 표시가 완료되기 전에 일반적인 어린 세대 전용 수집이 발생할 수 있습니다. 표시는 두 개의 특별한 정지-더-월드(Pause) 일시 정지인 다시 표시(Remark)와 정리(Cleanup)로 완료됩니다.
-
다시 표시: 이 일시 정지는 표시 자체를 완료하고 전역 참조 처리 및 클래스 언로딩을 수행합니다. 다시 표시와 정리(Cleanup) 사이에서 G1은 동시에 생명 정보 요약을 계산하며, 이는 정리 일시 정지에서 내부 데이터 구조를 업데이트하는 데 사용됩니다.
-
Cleanup: 이 일시 중지는 완전히 비어 있는 영역도 책정하고, 실제로 공간 회수 단계를 따르는지 여부를 결정합니다. 공간 회수 단계가 이어진다면, young-only 단계는 단일 young-only 수집으로 완료됩니다.
-
공간 회수 단계: 이 단계는 여러 혼합 수집으로 구성됩니다. young 세대 영역뿐만 아니라 old 세대 영역의 살아 있는 객체도 이동시킵니다. 공간 회수 단계는 G1이 더 많은 old 세대 영역을 이동시켜도 충분한 노력 가치가 되는 만큼 충분한 여유 공간을 생성하지 않는 것으로 판단될 때 종료됩니다.
G1은 –XX:+UseG1GC
플래그를 사용하여 활성화할 수 있습니다. 이 전략은 백그라운드 스레드가 도달할 수 없는 객체를 스캔하기 전에 힙이 고갈되는 가능성을 줄였습니다. 또한 이는 CMS 수집기가 STW 모드에서만 수행할 수 있는 힙을 실시간으로 압축합니다. Java 8에서는 G1 수집기로 명명된 아름다운 최적화가 제공되는데, 이를 문자열 중복 제거라고 합니다. 우리가 아는대로 문자 배열은 문자열을 나타내는 데 많은 힙 공간을 차지합니다. G1 수집기를 최적화하여 힙 전체에서 둘 이상 중복된 문자열을 식별하고 여러 번 복사되지 않도록 동일한 내부 char[] 배열을 가리키도록 수정할 수 있습니다. 이 최적화를 활성화하려면 -XX:+UseStringDeduplication
JVM 인수를 사용할 수 있습니다. G1은 JDK 9에서 기본 가비지 수집기입니다.
Java 8 PermGen 및 Metaspace
다시 언급했듯이 Java 8 이후로 Permanent Generation 공간이 제거되었습니다. 따라서 JDK 8 HotSpot JVM은 이제 클래스 메타데이터의 표현을 위해 Metaspace라고 불리는 네이티브 메모리를 사용합니다. 클래스 메타데이터에 대한 대부분의 할당은 네이티브 메모리에서 이루어집니다. 또한, 클래스 메타데이터에 사용되는 메모리 양을 제한하기 위한 MaxMetaspaceSize라는 새로운 플래그가 있습니다. 이 값을 지정하지 않으면 Metaspace는 실행 중인 응용 프로그램의 수요에 따라 런타임에서 크기를 조정합니다. 클래스 메타데이터 사용량이 MaxMetaspaceSize 제한에 도달하면 Metaspace 가비지 컬렉션이 트리거됩니다. 과도한 Metaspace 가비지 컬렉션은 클래스, 클래스로더 메모리 누수 또는 응용 프로그램 크기 지정이 부적절한 증상일 수 있습니다. 이로써 Java에서의 Garbage Collection에 대한 내용이 끝났습니다. 여러분이 Java에서 가지고 있는 다양한 가비지 컬렉터에 대한 이해를 얻었으면 좋겠습니다. 참고 자료: 오라클 문서, G1 GC.
Source:
https://www.digitalocean.com/community/tutorials/garbage-collection-in-java