Java 17에서 G1GC 옵션 종류, 특징 및 튜닝 방법
Java는 오랜 기간 동안 다양한 Garbage Collector(이하 GC)를 제공해 왔으며, Java 17에서는 G1(이하 G1GC)이 기본 GC로 사용됩니다. G1GC는 초단위 이하의 짧은 GC 정지 시간(pause time)을 목적으로, 기존의 Parallel GC나 CMS(Concurrent Mark Sweep) 등의 문제점을 보완하여 고성능과 안정성을 확보하고자 만든 가비지 컬렉터입니다. 이번 블로그 게시물을 통해 G1GC가 어떻게 동작하고, 어떤 옵션들을 활용할 수 있으며, 규모가 다른 환경에서 어떻게 튜닝할 수 있는지 알아보겠습니다.
G1GC의 기본 작동 원리
G1GC(Garbage-First Garbage Collector)는 힙(Heap)을 여러 “Region”으로 나누어, 살아 있는 객체의 양에 따라 우선순위가 높은 Region부터 먼저 청소(컬렉션)하는 방식으로 동작합니다. 다음은 주요 특징입니다:
-
Region 단위 관리
크게 한 덩어리의 힙을 여러 개의 작은 Region으로 분할합니다. 각 Region은 기본적으로 동일한 크기를 갖지만(기본 크기는 1MB ~ 32MB까지 자동 결정됨), G1GC는 각 Region을 독립적으로 청소해 메모리 단편화를 완화합니다. -
Concurrent & Incremental Collection
Stop-the-world(이하 STW) 시간을 줄이기 위해 앱 스레드와 동시에(Concurrent) 백그라운드로 작업을 진행합니다. 따라서 메모리 사용량이 늘어나도, 짧은 간격으로 GC를 나누어 수행해 긴 시간 멈추는 일을 최소화하려 합니다. -
Pause Time 목표 설정
G1GC는 -XX:MaxGCPauseMillis-XX:MaxGCPauseMillis 을 통해 GC 정지 시간의 목표치를 설정할 수 있습니다. 가능한 한 설정된 목표 시간 안에 GC가 끝나도록 Region을 선택해 부분 수집(Partial Collection)을 수행합니다. -
Garbage-First 전략
한 번의 GC 마다 여러 Region 중에서 가장 부채비율(survivor ratio)이 낮거나, “수집 가치가 높다”고 판단되는 영역부터 우선적으로 수집합니다. 이를 통해 전체적인 힙 효율을 높이는 전략을 취합니다.
G1GC의 특징
G1GC는 다음과 같은 특징을 가지고 있습니다:
-
영역 기반 메모리 관리
G1GC는 힙 메모리를 고정 크기의 영역(Region)으로 나누어 관리합니다. 각 영역은 Eden, Survivor, Old 영역 등으로 사용됩니다. -
Predictable Pause Time
애플리케이션의 지연 시간을 최소화하기 위해 Pause Time Goal을 기준으로 가비지 컬렉션 작업을 조정합니다. -
Concurrent Marking
힙 영역 중에서 가비지가 많이 포함된 영역을 우선적으로 정리하여 효율적인 메모리 관리를 제공합니다. -
Evacuation Pause
복사 기반의 GC 작업으로 Fragmentation 문제를 최소화합니다.
G1GC 주요 옵션 및 기본값
아래 표는 G1GC에서 자주 쓰이는 옵션과 기본값, 그리고 간단한 설명을 정리한 것입니다.
| 옵션 | 기본값 | 설명 |
|---|---|---|
| -XX:+UseG1GC | Java 17 이후 기본 활성화 | Java 17의 기본 GC. 다른 GC와 혼용하지 않고, G1GC를 명시적으로 사용하려 할 때 지정할 수 있음. |
| -XX:MaxGCPauseMillis= | 200ms | STW(Stop-The-World) 시간의 목표치를 밀리초로 설정. 예) -XX:MaxGCPauseMillis=200 ⇒ 최대 200ms를 목표로 |
| -XX:InitiatingHeapOccupancyPercent= | 45 | 힙 사용률이 특정 %에 도달했을 때 백그라운드(Concurrent) GC 사이클을 시작. 기본값(45)보다 낮추면 GC를 더 일찍 시작해 메모리 고갈을 예방. |
| -XX:G1ReservePercent= | 10 | GC 후에도 여유 Region을 유지해 급격한 객체 생성을 대비. 너무 높으면 실제 사용 가능한 힙이 줄어듦. |
| -XX:G1HeapRegionSize= | 자동 결정 (1MB ~ 32MB) | Region 하나의 크기를 수동으로 지정할 때 사용. 일반적으로는 자동 결정이 적절함. |
| -XX:+ParallelRefProcEnabled | true (기본적으로 활성화) | Reference(Soft, Weak 등) 처리를 병렬화하여 GC 시간을 단축. |
| -XX:+DisableExplicitGC | false (비활성화) | System.gc() 호출을 무시해 불필요한 GC STW를 방지. G1GC 환경에서는 권장되는 설정이나, 필요에 따라 조정 가능. |
시스템 규모별 튜닝 방법 요약
다음 표는 애플리케이션 메모리 크기와 트래픽 수준에 따라 G1GC를 어떻게 조정할 수 있는지 간단히 제시합니다. 실제로는 애플리케이션 특성, 자원 환경, JVM 로그 분석 결과 등을 종합적으로 판단해야 합니다.
| 시스템 규모 | 주요 전략 | 상세 설명 |
|---|---|---|
| 소규모 서비스 (수백 MB ~ 수 GB) | 기본 옵션 사용 | Java 17 기본 G1GC 설정을 그대로 사용해도 무난함. GC 로그 모니터링( -Xlog:gc* )으로 STW 시간과 빈도를 관찰하며, 문제 없으면 유지. |
| 중간 규모 서비스 (수 GB ~ 수십 GB) | -XX:MaxGCPauseMillis=200 정도로 시작 -XX:InitiatingHeapOccupancyPercent=30~40 |
응답 시간을 중요시한다면 100~200ms 정도 STW 목표를 잡고 모니터링. 트래픽 변동이 커질 때를 대비해 기본값(45)보다 낮은 Initiating Heap Occupancy Percent를 적용, GC를 자주 일찍 시작해 Full GC 방지. |
| 대규모 서비스 (수십 GB ~ 수백 GB) | -XX:MaxGCPauseMillis=100~200 GC 병렬 스레드(ParallelGCThreads, ConcGCThreads) 조절 -XX:G1ReservePercent=15~20 정도로 상향 |
코어 수가 많은 환경이라면 병렬 GC 스레드를 늘려서 효율 향상. 초당 대규모 객체 생성이 발생할 수 있으므로 Reserve Percent를 넉넉히 잡아 Full GC 위험을 줄임. STW 목표를 엄격하게(100ms 등) 설정하고 단계적으로 검증. |
효과적인 모니터링과 진단
- GC 로그 분석
“Young GC” 발생 빈도, “Full GC” 발생 여부, STW 시간을 확인하면서 튜닝 효과를 측정합니다.-Xlog:gc*:file=./gc.log:time -
Survivor 영역 확인
살아남는 객체가 많은지, Old 영역으로 빠르게 쌓이지 않는지 관찰하여 Survivor 영역이 충분한지 점검합니다. - 메타스페이스(Metaspace) 사용량 체크
클래스 로딩이 매우 빈번한 환경에서는 메타스페이스 사용이 증가할 수 있으므로, -XX:MaxMetaspaceSize 등도 함께 고려합니다.
마무리 및 결론
G1GC는 짧은 GC 정지 시간을 유지하면서도, 힙을 효율적으로 다루도록 설계되었습니다. Java 17에서 기본 GC로 이미 충분히 안정적으로 동작하지만, 시스템 규모와 서비스 특성에 맞춰 적절히 옵션을 조정하면 한층 더 나은 성능과 응답 속도를 기대할 수 있습니다.
- 먼저 기본 설정과 기본값을 충실히 사용
- 문제가 확인되면 한 가지씩 설정값 변경 후 로그 분석
- 정기적인 모니터링과 부하 테스트를 통해 튜닝 효과 확인
이러한 절차를 지키면, G1GC로 더욱 쾌적한 Java 애플리케이션 환경을 구축할 수 있을 것입니다.
Evacuation Pause 메커니즘 상세 이해
G1GC의 핵심 동작 중 하나인 Evacuation Pause는 STW(Stop-The-World)를 발생시키며, 이를 이해해야 효과적인 튜닝이 가능합니다.
Young-Only Phase (Minor GC)
[Evacuation Pause 흐름]
1. Young Region의 Eden이 가득 참
2. STW 발생
3. Eden + Survivor Region의 살아있는 객체를 새로운 Survivor Region으로 복사
4. 오래된 객체(Age 임계값 초과)는 Old Region으로 승격
5. 비워진 Eden Region은 해제
Mixed GC Phase
Concurrent Marking 이후 G1GC는 Young + Old Region을 함께 수집합니다.
[IHOP(Initiating Heap Occupancy Percent) 동작]
Old Generation 사용률이 IHOP(기본 45%)를 초과
→ Concurrent Marking 시작
→ Mixed Collection 준비
→ Young GC + Old Region 일부 수집 (Mixed GC)
→ Old 사용률이 충분히 낮아질 때까지 반복
IHOP 튜닝 전략:
- Full GC가 자주 발생하면: IHOP을 낮춤 (
-XX:InitiatingHeapOccupancyPercent=35) - Mixed GC가 너무 빈번하면: IHOP을 높임 (
-XX:InitiatingHeapOccupancyPercent=55)
Humongous Object 처리 및 튜닝
힙 리전 크기의 50%를 초과하는 객체는 Humongous Object로 처리됩니다. 이는 G1GC 성능에 큰 영향을 미칩니다.
리전 크기 = 16MB (예시)
Humongous 임계값 = 8MB
8MB 초과 객체 → Humongous Region에 직접 할당
→ Old Generation에 바로 배치 (Young GC 미거침)
→ Concurrent Marking 통해서만 회수
→ 자주 생성/해제 시 GC 압박 증가
Humongous Object 진단:
# GC 로그에서 Humongous 관련 메시지 확인
-Xlog:gc*,gc+heap=debug:file=gc.log
# 로그 예시
[gc,heap] GC(5) Humongous regions: 2->0
Humongous Object 튜닝:
# G1HeapRegionSize 증가로 Humongous 비율 감소
# 예: 대용량 byte[] 할당이 많은 경우
-XX:G1HeapRegionSize=32m # 32MB 이상 객체만 Humongous
# 또는 애플리케이션에서 객체 재사용 패턴 도입
# (ByteBuffer pooling, 객체 풀 활용)
실제 GC 로그 해석 예시 (Java 17 Unified Logging)
GC 로그 수집 설정
# Java 17 권장 GC 로그 옵션
-Xlog:gc*:file=/var/log/app/gc-%t.log:time,uptime,level,tags:filecount=5,filesize=20m
GC 로그 예시 및 해석
# Young GC (Evacuation Pause) 예시
[2026-03-23T10:15:23.123+0900][5.123s][info][gc] GC(42) Pause Young (Normal) (G1 Evacuation Pause) 512M->256M(2048M) 45.123ms
해석:
- GC(42): 42번째 GC
- Pause Young (Normal): Young Only GC
- 512M->256M(2048M): 힙 사용량 512MB → 256MB (전체 힙 2048MB)
- 45.123ms: STW 시간 (MaxGCPauseMillis=200 목표 대비 양호)
# Mixed GC 예시
[2026-03-23T10:16:00.456+0900][42.456s][info][gc] GC(87) Pause Young (Mixed) (G1 Evacuation Pause) 1024M->512M(2048M) 120.456ms
해석:
- (Mixed): Young + Old Region 함께 수집
- 120.456ms: 200ms 목표 근접 → IHOP 조정 검토 필요
# Full GC (비정상, 즉시 조사 필요)
[2026-03-23T10:20:00.789+0900][242.789s][info][gc] GC(150) Pause Full (G1 Evacuation Failed) 1900M->800M(2048M) 3245.678ms
해석:
- Full GC 발생: G1GC가 Evacuation 중 공간 부족
- 3245ms: 매우 긴 STW → 힙 사이즈 증가 또는 IHOP 조정 필요
스레드 수 튜닝
# 권장 설정
# CPU 코어 수: N
-XX:ParallelGCThreads=N*0.625 # STW 병렬 GC 스레드 수
-XX:ConcGCThreads=N/4 # Concurrent 마킹 스레드 수
# 예시: 8코어 서버
-XX:ParallelGCThreads=5 # 8 * 0.625 = 5
-XX:ConcGCThreads=2 # 8 / 4 = 2
| 파라미터 | 기본값 공식 | 영향 |
|---|---|---|
ParallelGCThreads |
min(8, cpus) + (cpus > 8 ? (cpus-8)*5/8 : 0) |
STW 시간 단축 (코어 많을수록 효과) |
ConcGCThreads |
max(1, ParallelGCThreads/4) |
애플리케이션 처리량에 영향 (높을수록 CPU 점유) |
Java 11 → Java 17 G1GC 주요 개선 사항
| 개선 항목 | Java 11 | Java 17 |
|---|---|---|
| Adaptive IHOP | 기본값 45% 고정 | 런타임에 적응적으로 자동 조정 (JEP 307) |
| Concurrent Class Unloading | 기본 비활성 | G1GC에서 기본 활성화 |
| Parallel Full GC | JDK 10에서 추가됨 | 더욱 안정화 |
| JEP 346: G1 Uncommit | 미지원 | Idle 시 힙 메모리를 OS에 반환 가능 |
| ZGC/Shenandoah | 실험적(Experimental) | Production Ready |
ZGC vs Shenandoah vs G1GC 비교
| 항목 | G1GC (기본) | ZGC | Shenandoah |
|---|---|---|---|
| Pause 목표 | 수백ms | < 1ms | < 10ms |
| 처리량 | 높음 | 약간 낮음 | 약간 낮음 |
| 메모리 오버헤드 | 낮음 | 중간 | 중간 |
| 적합 환경 | 범용 | 초저지연 (금융, 게임) | 저지연 + 대형 힙 |
| 활성화 | -XX:+UseG1GC |
-XX:+UseZGC |
-XX:+UseShenandoahGC |
댓글남기기