G1 GC 소개
Java7 부터 사용이 가능했고, Java9 부터 기본 GC 로 선정되었다. G1 GC 는 큰 메모리를 사용하는 멀티 프로세스 머신에 적합한 GC 이다.
설정된 목표 stop the world 시간을 높은 확률로 달성하려고 시도한다. 최대한 정지 시간을 예측 가능하고 짧게 유지하려는 목적이다.
높은 처리량을 목표로 하고 있으며, 사용자의 설정 필요성을 최소화하려고 한다. 개발자가 성능을 최적화하기 위해 많은 시간을 소비하지 않도록 하는것을 목표로 한다.
적용 대상으로는
- heap 크기가 수 GB ~ 최대 10GB 환경
- 실시간으로 객체 할당 및 프로모션 비율이 크게 변할 수 있는 환경
- heap 내에서 상당한 수준의 조각화(fragmentation)가 발생할 수 있는 환경
- 수백 밀리초를 넘지 않는 예측 가능한 stop the world 시간 목표가 필요한 경우
CMS(Concurrent Mark-Sweep) GC 을 대체한다.
heap 을 여러 영역으로 나누고, 가비지 컬렉션을 수행하는 동안 영역들을 동시에 처리하여 고성능을 달성하고자 한다.
G1 GC 활성화
기본 GC 로 별도의 설정이 필요하지는 않다. 명시적으로 -XX:+UseG1GC 통해 가능하다.
기본 개념
G1 도 다른 GC 와 마찬가지로 young generation, old generation 으로 메모리 생명주기에 세대 개념을 사용한다. 그리고 여러 스레드를 사용하여 점진적으로 가비지 컬렉션을 수행한다. 처리량 개선을 위해서 일부 가비지 컬렉션 작업은 어플리케이션이 실행중에도 진행할 수 있으며 일부 중요한 작업은 stop the world 를 발생시킨다.
Heap Layout
G1은 힙을 동일한 크기의 힙 영역 집합으로 분할하고, 각 영역은 인접한 범위의 가상 메모리로 구성된다. 영역은 메모리 할당 및 메모리 회수의 단위이다. 언제든지 영역은 비어있거나, young generation, old generation 으로 할당 할 수 있다. 메모리 요청이 들어오면, 메모리 관리자는 여유 영역을 할당한다. 메모리 관리자는 여유 영역을 generation에 할당하고 여유 영역을 어플리케이션에 반환하여 어플리케이션이 영역에 할당할 수 있도록 한다.
young generation 은 eden(에덴) 영역 (빨간색) 과 survivor 영역 (S가 쓰여진 빨간색) 으로 구성된다. 다른 GC 에서는 인접한 공간으로 구성하여 동일한 기능을 제공하는데 G1 GC 는 비연속적인 패턴으로 메모리에 배치하는 차이점이 있다.
old generation 은 그림에서 파란색으로 구성된다. 파란색의 H 같은 경우 여러 영역에 걸쳐 거대하게 차지할 수 있다.
어플리케이션은 항상 eden 영역인 young generation 에 할당한다. 매우 큰 객체는 예외적으로 old generation 에 바로 할당될 수 있다.
G1 GC pauses 는 young generation 을 전체적으로 공간 회수를 할 수 있고, old generation 의 공간도 반환할 수 있다. pause 가 진행되는 동안 G1 은 aging 을 사용하여 객체들을 힙의 다른 영역으로 복사한다. young generation 에 있던 객체는 survivor 나 old region 으로 복사되고, old regions 에 있던 객체들은 다른 old regions 으로 복사된다.
Garbage Collection Cycle
고수준에서 보았을 때, G1 은 아래의 이미지처럼 두 단계를 번갈아 가며 진행한다.
1. Young-Only
2. Space Reclamation
객체들을 old generation 으로 승격 작업으로 시작한다. old generation 의 메모리 점유가 특정한 한계점(the Initiating Heap Occupancy threshold.)에 도달했을 때 young-only phase 와 space-reclamation phase 의 전환이 일어난다. 이 때, G1 은 regular young-only collection 대신 Initial Mark young-only collection 을 스케줄링한다.
Initial Mark
marking process 와 regular young-only collection 을 같이 시작한다. 동시 마킹(Concurrent Marking) 은 old generation 에 있는 모든 live objects 들을 다음 space-reclamation phase 를 위해 유지할 지에 대해 결정한다. 마킹(Marking) 이 진행되는 동안에도, regular young collections 은 진행될 수 있다. 마킹(Marking) 이 Remark and Cleanup 이라는 두 개의 stop-the-world pauses 를 발생하면서 끝난다.
Remark
Remark pause 는 전역 참조 프로세싱과 클래스 언로딩을 수행하며 마킹(Marking) 을 마무리한다. Remark 와 Cleanup 사이에서 G1 은 동시에 liveness 정보의 요약을 계산하고 이 정보는 Cleanup pause 에서 내부 데이터 구조를 변경하는데 최종적으로 사용된다.
Cleanup
완전히 빈 영역이 회수 되며 이후 space-reclamation phase 가 진행될지 결정하는 pause 이다. space-reclamation phase 가 뒤따르게 되면, young-only phase 는 한 번의 young-only collection 과 끝이난다.
Space-reclamation phase
young generation 영역과 함께 old generation 영역의 객체들도 이동하게 만드는 Mixed collection 으로 구성된다. G1 은 old generation 영역의 충분한 여유 공간 확보를 할 수 없다고 판단할 때 이 단계는 끝이 난다. space-reclamation 이 끝난 후, collection cycle 은 또 다른 young-only phase 와 함께 다시 시작된다. 백업으로 liveness 정보를 수집하는 동안 어플리케이션이 메모리 부족이 발생하게 되면 G1 은 다른 GC 와 마찬가지로 in-place stop-the-world 전체 heap compaction (Full GC) 을 수행한다.
Garbage-First Internal
Determining Initial Heap Occupancy (IHOP)
Initial Mark collection 이 트리거되는 임계치이고 old generation 크기의 비율로 결정된다. 지연 시간을 예측하기 위해서 해당 개념을 사용한다. G1 은 최적의 IHOP 를 마킹 주기 동안 얼마나 마킹이 오래걸리는지와 얼마나 많은 메모리가 old generation 에 할당되는지 관찰을 통해서 결정한다. 이 기능은 Adaptive IHOP 라고 부른다. adaptive IHOP 가 활성화 되면, 임계치 예측을 잘 할 수 있을 만한 관찰을 하지 못하는 경우에 -XX:InitiatingHeapOccupancyPercent 옵션은 현재 old generation 크기의 비율(기본 45%)로 초기 값을 결정한다. -XX:-G1UseAdaptiveIHOP 옵션은 해당 기능을 사용하지 않을 때 사용한다. 비활성화 된 경우, -XX:InitiatingHeapOccupancyPercent 값은 항상 임계값을 결정한다. 내부적으로, Adaptive IHOP 는 old generation 점유가 현재 최대 old generation 크기에서 -XX:G1HeapReservePercent 값을 뺀 값이 추가 버퍼로 되었을 때 첫번째 space-reclamation 단계의 mixed GC 를 시작하기 위해서 IHOP 를 설정하려고 한다.
마킹(Marking)
G1 마킹은 Snapshot-At-The-Beginning (SATB) 라는 알고리즘을 사용한다. 마킹 시작 시 살아있는 모든 객체들이 마킹에서 제외 되어 살아 남았을 때, Initial Mark pause 시간에 힙의 가상 스냅샷을 만든다. 마킹 동안 수집 대상이 된 객체들은 여전히 space-reclamation 단계를 위해서 여전히 살아 있는 객체로 간주되는 것을 의미한다. 이로 인해 다른 GC 에 비해 일부 추가적인 메모리 잘못 사용될 수 있지만, SATB 는 Remark pause 동안 잠재적으로 적은 지연시간을 제공한다. 마킹에서 너무 보수적으로 살아있는 객체로 간주되어도 이후 마킹에서 회수된다.
Heap 메모리 여유가 없 상황
너무 많은 메모리를 사용하면서 heap 메모리가 다소 타이트한 상황에서 어플리케이션이 계속 동작할 때 Evacuation 이 실패할 수 있다. G1 은 이미 이동된 객체는 이동된 채로 두고 이동되지 않은 객체는 그대로 두고 객체 사이의 참조만 조정한 채로 수집을 완료하려고 한다. Evacuation 실패는 추가적인 overhead 를 발생할 수 있지만, 일반적으로 다른 young 수집만큼 빠르게 진행되어 한다. evacuation 실패와 함께 끝난 수집 후에, G1 은 다른 측정없이 어플리케이션을 정상처럼 재개한다. G1 은 evacuation 실패는 수집이 마무리에 접어들었을 때 발생했다고 간주한다. 즉, 대부분의 객체는 이미 이동에 성공했고, 어플리케이션이 마킹이 완료되고 space-reclamation 이 시작할 때 까지 계속 동작할 만큼의 충분한 공간이 있다고 가정한다. 이 가정이 성립되지 않는다면, G1 은 최종적으로 전체 heap 에 대해서 in-place compaction 을 수행하는 매우 느린 Full GC 를 예약한다.
Humongous Objects
region 의 절반 크기 이상의 객체가 Humongous objects 이다. 현재 region 크기는 -XX:G1HeapRegionSize 옵션으로 설정하지 않는다면, ergonomically 하게 G1 이 결정한다.
이러한 큰 객체들은 때때로 특별한 방법으로 관리된다.
- old generation 에 연속되는 regions 에 할당된다.
- 일반적으로, humongous 객체는 Cleanup pause 동안에 마킹 중 끝에 마지막에 또는 Full GC 중에만 회수 될 수 있다.
- 많은 객체들에 의해 참조되지 않는 primitive type 이 배열 같은 경우, G1은 모든 종류의 가비지 컬렉션 pause 에 기회를 보다가 humongous 객체들 회수하려고 시도한다. 이러한 기능은 기본적으로 활성화 되어 있지만 -XX:G1EagerreclaimHumongousObjects 옵션을 통해서 비활성화 할 수 있다.
- G1 은 Initiating Heap Occupancy threshold 를 humongous object 가 할당될 때마다 검사하고 만약 threshold 를 초과하는 경우 initial mark young collection 을 즉시 강제할 수 있기 때문에 humongous 객체 할당은 섣부른 gc pause 로 이어질 수 있다.
- humongous 객체는 절대 이동하지 않는다. 심지어 Full GC 동안에도 이동하지 않는다. 많은 free region 이 있더라도 이로 인해 공간 조각화로 인해 조기에 느린 Full GC가 발생하거나 예상치 못한 메모리 부족 상태가 발생할 수 있다.
Young-Only Phase Generation Sizing
young-only phase 가 진행되는 동안, collection set 다시 말해 수집하려는 regions 들의 집합은 young generation region 으로만 이루어진다. G1은 항상 young generation 의 크기를 young-only collection 마지막에 조정한다. G1 은 실제 pause 시간을 장기적으로 관찰한다. 그리고 G1은 설정된 -XX:MaxGCPausetimeMillis 과 -XX:PausetimeIntervalMillis pause 목표 시간을 달성하기 위해 young generation 크기를 조정한다. G1은 비슷한 크기의 young generation 에 걸린 pause 시간과 수집 동안 얼마나 많은 객체들이 다른 영역으로 이동했는지 그리고 얼마나 서로 관련되어 있는지에 대해서도 고려한다. G1 은 설정된 -XX:G1NewSizePercent 와 -XX:G1MaxNewSizePercent 로 목표 pause 시간을 달성하기 위해서 young generation 의 크기를 조절한다.
Space-Reclamation Phase Generation Sizing
space-reclamation 은 한 번의 gc pause 동 old generation 에 있는 가능한 많은 공간의 양을 회수하는 목표로 진행한다. young generation 의 크기는 최소한의 허용치로 설정된다. 통상적으로 -XX:G1NewSizePercent 옵션을 통해 결정된다. G1 은 목표 pause 시간을 초과하지 않는 범위 내에서 계속하여 old generation 영역을 추가한다. G1 은 회수 효율성이 높은 순서대로 old generation 을 추가한다. 가장 효율성이 높은 영역부터 시작해서, 남은 pause 시간을 고려해 최종 회수할 영역의 집합을 결정한다.
-XX:G1MixedGCCountTarget 옵션은 space-reclamation 단계의 길이를 설정한다. 설정에 따라 수집 당 회수할 old generation 의 최소 수가 결정된다. 회수 대상 후보 영역은 space-reclmation 단계 시작 시 -XX:G1MixedGCLiveThresholdPercent 보다 점유율이 낮은 모든 old generation 영역이다. space-reclamation 단계 는 회수 대상 후보 영역에서 회수할 수 있는 남은 공간의 양이 -XX:G1HeapWastePercent 로 설정된 백분율보다 낮아지면 종료한다.
다른 GC 와의 차이점
Parallel GC 은 old generation 을 전체적으로 압축하고 회수하는데, G1 GC 점진적으로 여러번에 걸쳐서 짧은 수집을 진행한다. 이를 통해서 G1 은 처리량을 희생하면서 짧은 pause 시간을 달성한다.
CMS GC 와 유사하게 G1 GC 는 동시에 old generation space-reclamation 을 진행한다. 하지만 CMS GC 는 old generation 을 조각낼 수 없기에, 결국 긴 Full GC 로 이어진다.
G1 은 다른 GC 보다 동시에 수집을 진행하기 때문에 처리량에 보다 오버헤드가 발생할 수 있다.
이러한 작동방식 때문에, G1 은 특별한 메카니즘을 통해서 효율성을 높인다.
G1 은 old generation 에 완전히 비어있는 큰 영역을 어떤 수집에서든 회수할 수 있다. 이를 통해, 많은 양의 메모리를 확보하여 할당할 수 있게 한다. 이 기능은 기본으로 활성화 되어 있다. -XX:-G1EagerReclaimHumongousObjects 옵션을 통해 비활성화 할 수 있다.
G1 은 동시에 문자열 중복을 제거할 수 있다. 이 기능은 기본적으로 비활성화 되어 있다. -XX:+G1EnableStringDeduplication 을 통해 활성화 할 수 있다.
참고
https://docs.oracle.com/javase/9/gctuning/garbage-first-garbage-collector.htm
https://docs.oracle.com/javase/9/gctuning/garbage-first-garbage-collector-tuning.htm#GUID-90E30ACA-8040-432E-B3A0-1E0440AB556A
댓글
댓글 쓰기