G1GC
G1GC는 이름 그대로 Garbage First 전략을 사용한다.
즉 많은 오브젝트가 모여있는 region을 먼저 수집해서 전체 pause time을 제어하는 gc
이를 위해 g1은 다음 목표를 잡는다.
- Pause Time 예측 가능 (Predictable Pause Time)
- Heap 전체를 균등 크기의 Region으로 나누기
- Young/Old 세대를 명확히 구분하되, Old 영역도 Region 단위로 수집 가능
- Old 영역도 Compaction을 포함한 Evacuation 기반으로 처리
- Full GC를 최대한 피하고, Mixed Collection으로 대체
즉 region 기반 + incremental, evacuation 기반 Pause Time Control Collector
evacuation(피난 이동): 살아있는 객체를 새로운 메로리 영역으로 복사하고 원래 있던 그 자리에서 전체 폐기하는 방식의 gc 동작.
Incremental의 의미: 두가지가 있는데 gc를 여러 조각 piece로 나눠 실행하다는 뜻으로 한 번에 힙 전체를 stw처리 하지 않고 적은 양의 작업을 여러번 반복한다는 의미, 혹은 증분적으로 evacuation을 실행한다는 의미. 즉 핵심은 일괄 처리를 안하고 부분적으로 싫행한다는 의미다.
Region 기반 구조 #
g1gc의 핵심은 고정크기 region(일반적으로 1 ~ 32mb) 단위로 힙을 나누는 것이다.
region 종류 #
- young region: eden/survivor
- old region
- humongous region: region 하나에 못 들어갈 만큼 큰 객체 (> 50% region size) 저장: 사실상 old처리와 유사하지만 full gc 트리거가 더 쉬움
region 단위로 살아있는 객체 비율 live ratio를 추적한다.
Major 구조: Young Collection, Mixed Collection #
g1gc에는 크게 두 타입의 정규 수집이 있다.
young collection (minor gc)
- Eden -> Survivor (Evacuate)
- Survivor의 객체는 살아남으면 old로 promotion
- region 단위의 evacuation이므로 항상 compaction이 자동이다.
- 단편화가 없다는 뜻
mixed gc
- old영역도 young처럼 evacuate 식으로 일부 region만 선택해 수집한다. 이게 g1gc가 stw full compaction이 아닌 major gc를 수행하는 방식
Mixed GC는 시작 기준이 Marking Cycle 종료 시점이다
Concurrent Marking Cycle (Old 수집 시작점) #
g1의 진짜 핵심인데
old 영역을 자르는 기준은 어떤 객체가 살아있는지에 대한 정확한 Mark Bitmap.
Marking Cycle은 다음 단계로 구성된다.
1. Initial Mark (STW)
2. Root Region Scan (Concurrent)
3. Concurrent Mark (Concurrent)
4. Remark (STW)
5. Cleanup (Concurrent + 일부 STW)

- Initial Mark: young gc와 같이 짧은 stw기간동안 root set을 기반으로 기본 mark 시작점만 표시한다 pause 매우 짧음
- Concurrent Mark: 동시에 애플리케이션을 실행시키며 old 객체 그래프를 따라가며 mark bitmap에 기록한다.
- remark: SATB 버퍼를 처리하며 놓쳤을 수 있는 marking을 보완한다. stw지만 매우 최적화되어 있어서 짧다.
- Cleanup
- Live Ratio 계산
- Live Ratio가 낮은 Region을 Mixed GC 후보로 등록한다
- 대부분 concurrent로 작업한다.
SATB(Snaposhot-At-The-Beginning) Marking #
concurrent marking에서 중요한 부분이다.
g1은 marking cycle 시작 시점의 heap snapshot을 기준으로 살아있는 객체를 판단하려고 한다. 문제는 마킹 중에 지속적으로 객체들의 참조가 바뀐다는 점이고
그래서 write barrier를 사용해 변경되기전 old 참조를 추적한다.
SATB 동작 개념
- 마킹 사이클중에 객체 필드가 다른 객체로 변경되면 그 필드에 있던 원래 객체 oldVal을 SATB버퍼에 집어넣는다.
- 이유는 marking이 시작될 당시 old는 살아있을수도 있었던 객체라서
즉, g1gc는 새 참조가 아니라 사라지는 참조를 추적한다.
이게 satb고 marking이 시작되는 순간 heap snapshot을 유지하려는것이 목적이다.
Write Barrier 구조 #
g1gc write barrier는 SATB용 wrtie barrier + Card Table Dirtying 두가지 모두 포함한다.
SATB Write Barrier
if (object.field ← new_ref) {
enqueue(old_ref) // old reference를 SATB queue에 넣음
}
Card Table Write Barrier: Old -> Young 참조가 생길때 Card를 Dirty 표시하여 나중에 Remark 단계에서 사용.
즉 write barrier는 두 가지 목적을 동시에 수행한다.
1) SATB Snapshot 유지 (old ref 기록)
2) Remembered Set 업데이트 (Card Table dirty)
Remembered Set(RSet) #
Region 단위 gc를 하기 위해서는 해당 region 밖에서 들어오는 참조를 알아야한다.
이를 위해 region마다 remembered set을 둔다.
RSet은 Card Table기반으로 누가 나를 참조하고 있는지를 기록한 구조고 region 단위 evacuation을 가능하게 만드는 핵심 구조다.
Evacuation #
g1은 full gc가 아닌 한 대부분 evacuation(비움, 피난 이동) 기반인데
evacuation = 살아있는 객체를 새 region으로 옮기고 기존 region은 바로 reclaim(회수).
이 과정은 하상 compaction을 동반하므로 파편화가 사실 존재하지 않는다. 이게 cms랑 큰차이
Pause Time Control #
G1은 여러 Region 중 Live Ratio가 가장 낮은 Region을 골라서 Evacuation 한다.
Evacuation 비용은 예측 가능하므로 G1은 Pause Time Goal에 맞추어 이번 gc에서 몇개 region만 처리할지를 결정한다.
목표 Pause = 200ms
Region 하나 Evacuation = 8ms
→ 200ms / 8ms = 25개 Region만 Evacuate
-XX:MaxGCPauseMillis=<ms>
위처럼 줄수있음 목표 퍼즈타임을.
Humongous Region 처리 #
큰 객체는 여러 region을 차지하거나 humongus region 한칸을 차지함
humongous는 fragmentation이 원인이며, mixed gc로 제거하기 어렵기 때문에 full gc가 트리거 되는 주요 원인이다.
Full GC fallback #
g1이라도 full gc가 발생한다.
- humongous region이 너무 많고 비효율적일때
- evacuation 실패 (새 region 부족)
- RSet 크기 폭증
- SATB queue overflow
- Native Memory 부족
- Old Region Live Ratio가 계속 높아 Mixed GC가 효과 없을때
g1 full gc는 기존 serial full gc가 유사한 stw compacting 알고리즘을 사용한다.
STW 지점 정리 #
g1gc의 모든 단계중 stw는 아래뿐이다.
- inital mark
- remark
- evacuation (수집)
- 일부 cleanup
- full gc 전체
그 외 대부분은 concurrent
g1gc 고질적 비용 포인트 #
다음 요소들이 실제로 g1 성능을 결정함
- Remembered Set Maintenance 비용
- Write Barrier 부하 (SATB + Card Table)
- Evacuation bandwidth(대역폭)
- Mixed GC 수집성 저하(Old region이 너무 살아있음)
- Humongous 객체 비율
이 비용 때문에 JVM 옵션에서 다음 튜닝을 자주 한다.
-XX:G1HeapRegionSize
-XX:MaxGCPauseMillis
-XX:G1NewSizePercent, -XX:G1MaxNewSizePercent
-XX:G1ReservePercent
-XX:G1HeapWastePercent
-XX:+UseStringDeduplication
흐름도
[Young GC 반복]
-> Eden/S survivor Evacuation
[Old 영역 증가 → Concurrent Marking Cycle 시작]
1) Initial Mark (STW, 매우 짧음)
2) Concurrent Mark (SATB 기반)
3) Final Remark (STW)
4) Cleanup (Mostly Concurrent)
5) Old Region 중 Live Ratio 낮은 후보 수집 등록
[Mixed GC 반복]
-> Young + 일부 Old Region Evacuation
[데이터 압축 + Region 재활용]
[Full GC fallback 발생 가능]