About G1 GC
들어가며
Java 8의 기본 Garbage Collector였던 CMS GC는 Java 9에서 deprecated되었고 Java 14에서 drop되었다. 본 문서에서는 CMS GC를 대체하는 새로운 기본 GC인 G1 GC가 CMS GC의 단점을 어떻게 보완하는지 알아보기로 한다.
1. 왜 G1 GC인가
1.1 CMS GC의 한계
CMS GC(Concurrent Mark-Sweep Garbage Collector)의 주 목적은 애플리케이션의 중단 시간을 최소화하여 성능을 개선하는 것이다.
- CMS GC의 동작
- Initial Mark: 짧은 시간 동안 애플리케이션을 멈추고(Stop-The-World; 이하 ‘STW’) 루트 영역에서 직접 참조되는 객체를 1차적으로 식별한다.
- Concurrent Mark: 애플리케이션이 실행되는 동시에 힙의 모든 객체를 순회하여 사용 중인 객체를 식별한다.
- Remark: Cuncurrent Mark 동안 참조가 변경된 객체를 STW 동안 재식별한다.
- Concurrent Sweep: 살아있지 않은 객체의 메모리를 회수한다.
Remark 과정은 전체 힙 크기가 크거나 참조 관계가 복잡할수록 긴 STW를 필요로 한다. 또한 힙의 빈 공간을 효율적으로 처리하지 못해 파편화가 생기고, 이로 인해 메모리 압박이 심해지면 compaction을 위한 Full GC가 빈번히 발생할 수 있다.
2. G1 GC에서 달라진 점
이러한 CMS GC의 단점을 보완할 수 있는 G1 GC(Garbage-First Garbage Collector)의 특징이 있다.
2.1 Region-based Management
G1 GC는 힙을 일정한 크기의 리전(Region)으로 분할한다. 각각의 리전은 Young 또는 Old Generation이 될 수 있고, 하나의 리전이 GC가 수행되는 최소 단위가 된다. G1 GC는 이 리전들을 가비지의 양과 종류에 따라 우선순위를 두어 효율적으로 처리한다.
리전은 통상의 GC와 마찬가지로 young generation, old generation으로 나뉘며 이들 영역의 위치는 정해져 있지 않고 메모리 상태에 따라 유동적으로 정해진다.
- Young generation
- Eden: 객체가 최초에 생성되는 영역
- Survivor: Eden에서 생존한 객체가 이동되는 영역
- Old generation
- 일정 시간 이상 살아남은 객체가 이동되는 영역
- Humongous
- 리전 크기의 절반 이상인 거대한 객체가 할당되는 영역
실제로 GC 작업이 수행될 때, GC를 수행할 리전의 집합을 Collection Set(이하 ‘CSet’)으로 정의한다. CSet은 메모리 상태를 최적화하고 GC 효율성을 높이기 위해 동적으로 결정된다.
2.2. Evacuation
G1 GC는 GC 과정에서 살아남은 객체를 새로운 위치로 대피(evacuate)시킨다. Evacuation은 Young generation의 Eden-Survivor0-Survivor1 영역 간에 일어나 이전 영역을 가비지 처리하는 동작에도 발생하고, Old generation에서도 살아남은 객체를 대피 시켜 메모리 파편화를 줄이고 전체적인 메모리 효율성을 향상 시킨다.
3. G1 GC의 동작
G1 GC는 크게 두 가지 단계로 구성된다. Young 객체를 확인하고 old generation으로 승격시키는 young-only phase와 실제로 공간 회수를 수행하는 space-reclamation phase를 실제 GC log와 함께 알아보자.
3.1 Young-only phase
3.1.1 Young GC
Young-only phase는 young 객체를 old generation으로 승격 시키는 Normal young GC 작업으로 시작한다. 참조가 유효하여 살아남은 young 객체는 Eden->Survivor#, Survivor0<->Survivor1, Survivor#->Old 의 방향으로 이동하게 된다.
- Young generation CSet: Eden, Survivor0, Survivor1에서 GC가 필요한 리전을 포함한다.
3.1.2 Concurrent Mark
Young GC 후에 old generation 점유율이 임계값을 넘어서면 concurrent start를 트리거한다.
- Concurrent Start: 이 단계에서는 실제로 old generation의 메모리를 회수하진 않지만 이후의 회수 단계에서 생존할 살아있는 객체를 식별한다. 루트 영역으로부터 살아있는 객체 탐색은 STW 없이 진행된다.
- 로그상 Concurrent Start와 Concurrent Cycle은 서로 다른 GC 순번을 가지고 있으나 동일한 트리거로 수행된다.
- Remark: Concurrent Mark 중에 변경된 참조를 반영하여 재탐색한다. STW가 발생한다.
- Cleanup: Mark 결과를 바탕으로 old generation의 메모리를 정리하고 회수할 준비를 한다. 실제로 메모리 회수가 필요하다고 판단될 때 Mixed GC를 트리거한다.
3.2 Space-reclamation phase
3.2.1 Prepare Mixed
Young, old generation 모두를 대상으로 하는 Mixed GC를 준비한다.
3.2.2 Mixed GC
Young, old generation에서 동시에 가비지를 탐색하며, 살아있는 객체를 이동시키거나 죽은 객체를 old generation에서 제거한다.
- Old Generation CSet: Old generation에서 GC가 필요한 리전을 포함하며, mixed GC 또는 full GC 시에 정의된다.
3.2.2 Full GC
Young GC에서 young 객체를 old로 이동시킬 수 없을 만큼 old generation의 메모리가 부족하거나, old GC(mixed 또는 full GC) 수행 후에도 old generation의 메모리를 확보할 수 없을 때 full GC가 발생한다. 이때는 heap compaction으로 가용 메모리를 확보한다.
통상적으로는 old GC로 old generation이 확보되므로 full GC가 트리거되는 경우가 많지 않으나 메모리 회수보다 빠르게 너무 많은 객체가 할당될 때, 거대한 객체가 많이 할당될 때, 메모리 누수가 발생할 때 등 여러 경우에 full GC가 발생할 수 있다.
4. G1 GC options
G1 GC는 특별한 옵션을 사용하지 않아도 전반적으로 좋은 성능을 제공한다. 애플리케이션의 목적에 따라 몇 가지 옵션을 추가할 수 있다.
4.1 거대한 객체의 수를 조절하려면
G1 GC에서 리전 크기의 절반을 넘는 거대한 객체는 humongous region에 할당된다. Humongous 객체가 많으면 이를 처리하기 위해 더 많은 메모리 영역을 할당하고 관리해야 한다. 메모리 압축(compaction) 과정이 어렵고 시간이 오래 걸릴 수 있다.
GC log를 살피면 이 거대한 객체가 차지하는 영역의 양을 알 수 있다.
리전 크기(기본값: 1M)를 늘리면 기존에 humongous로 할당되던 객체가 더 이상 humongous가 아니게 될 수 있다. 이 변경은 메모리 사용에 직접적인 영향을 미치지는 않으나 새로운 객체의 할당 방식과 GC 과정에 영향을 미치게 된다. Humongous region 자체는 줄어들 수 있으나 메모리 파편화의 위험성이 커지고, 특히 메모리 사용 패턴이 일정하지 않은 애플리케이션은 메모리 사용이 더 비효율적이 될 수도 있다. 또한 GC가 한 번에 처리해야 할 영역의 크기가 커지는 것이므로 더 많은 시간이 소모될 수도 있다. 따라서 애플리케이션의 메모리 사용 방식을 고려하여 값을 지정하여야 한다.
4.2 최대 정지 시간을 조절하려면
Max pause 시간을 조절하여(기본값: 200밀리초) 지연 시간을 최소화하거나 반대로 시간이 걸리더라도 한 번에 많은 처리를 할 수 있다.
4.2.1 Young generation 크기 설정은 하지 않는다
최대 정지 시간을 설정했다면 G1 GC는 자동으로 young generation의 크기를 결정하게 된다. 만약 이 옵션을 명시할 경우 최대 정지 시간 옵션이 정상적으로 작동하지 않는다.
4.3 Mixed GC의 작업량을 조절하려면
Mixed GC가 한 번 수행될 때 처리할 리전의 개수(기본값: 8)를 조절한다. 이 값이 줄어들면 1회의 Mixed GC가 빠르게 끝나므로 가용 메모리를 빠르게 확보할 수 있으나, 전체 영역을 처리하려면 Mixed GC가 여러 번에 거쳐 수행되므로 STW 시간이 더 길어질 수 있다.
반대로 이 값이 커져 만약 최대 중단 시간(MaxGCPauseMillis) 내에 처리할 수 없다면 MaxGCPauseMillis 값이 우선 시 된다.
마치며
최근에는 하드웨어의 발전으로 대형 엔터프라이즈 시스템에서도 자바가 많이 쓰이고 있다. 이에 따라 적절한 GC의 선택과 사용 또한 중요해진다.
G1 GC는 중단 시간(STW)을 최소화하고 메모리 파편화를 방지할 수 있어 현장에서 널리 사용되고 있는 만큼, G1 GC에 대한 깊은 이해는 여러분의 시스템 성능을 한층 높이는 데 도움이 될 것이다.
# References
https://docs.oracle.com/en/java/javase/11/gctuning/garbage-first-g1-garbage-collector1.html#GUID-0394E76A-1A8F-425E-A0D0-B48A3DC82B42
https://docs.oracle.com/en/java/javase/11/gctuning/garbage-first-garbage-collector-tuning.html#GUID-90E30ACA-8040-432E-B3A0-1E0440AB556A
https://www.redhat.com/en/blog/collecting-and-reading-g1-garbage-collector-logs-part
김민경 프로
플랫폼사업팀
에스코어의 CDC 솔루션 8Sync의 품질 점검 및 기술지원 업무를 담당하고 있습니다.
Register for Download Contents
- 이메일 주소를 제출해 주시면 콘텐츠를 다운로드 받을 수 있으며, 자동으로 뉴스레터 신청 서비스에 가입됩니다.
- 뉴스레터 서비스 가입 거부 시 콘텐츠 다운로드 서비스가 제한될 수 있습니다.
- 파일 다운로드가 되지 않을 경우 s-core_mktg@samsung.com으로 문의해주십시오.