GC가 뭔지는 알겠는데, 운영 환경에서 GC 때문에 응답이 느려지면 어디서부터 손대야 할까?

GC의 종류와 동작 원리를 아는 것은 시작일 뿐이다. 실제로 성능 문제를 겪었을 때 GC 로그를 읽고, 원인을 파악하고, 적절한 파라미터를 조정할 수 있어야 한다. G1 GC와 ZGC를 중심으로 실전 튜닝을 다룬다.

GC 로그 활성화

Java 9+ (Unified Logging)

BASH
java -Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=5,filesize=10M \
     -jar myapp.jar
옵션의미
gc*GC 관련 모든 로그
file=gc.log파일로 출력
time,uptime,level,tags타임스탬프, 가동 시간, 레벨, 태그 포함
filecount=5,filesize=10M최대 5개 파일, 각 10MB 로테이션

추가 유용한 옵션

BASH
# GC 일시 정지 시간 상세
-Xlog:gc+phases=debug

# 힙 영역 상세 (G1)
-Xlog:gc+heap=info

# GC 원인 표시
-Xlog:gc+cause=info

# 세이프포인트 정보
-Xlog:safepoint=info

GC 로그 읽는 법

G1 GC 로그 예시

PLAINTEXT
[2026-03-19T10:30:15.123+0900] GC(42) Pause Young (Normal) (G1 Evacuation Pause)
[2026-03-19T10:30:15.123+0900] GC(42)   Pre Evacuate Collection Set: 0.1ms
[2026-03-19T10:30:15.123+0900] GC(42)   Merge Heap Roots: 0.2ms
[2026-03-19T10:30:15.123+0900] GC(42)   Evacuate Collection Set: 8.5ms
[2026-03-19T10:30:15.123+0900] GC(42)   Post Evacuate Collection Set: 1.2ms
[2026-03-19T10:30:15.123+0900] GC(42)   Other: 0.3ms
[2026-03-19T10:30:15.123+0900] GC(42) Pause Young (Normal) 256M->48M(512M) 10.3ms

마지막 줄의 의미:

  • 256M->48M: 사용 메모리가 256MB에서 48MB로 감소
  • (512M): 전체 힙 크기
  • 10.3ms: GC 일시 정지 시간

주의해야 할 패턴

PLAINTEXT
# Full GC — 이게 자주 보이면 문제
GC(99) Pause Full (Allocation Failure) 480M->200M(512M) 1523.4ms

# to-space exhausted — 대피 실패
GC(55) To-space exhausted

# Humongous allocation — 대형 객체 할당
GC(33) Pause Young (Concurrent Start) (G1 Humongous Allocation)

G1 GC 튜닝

G1은 Java 9부터 기본 GC이며, 대부분의 워크로드에 적합합니다.

핵심 파라미터

BASH
java \
  -XX:+UseG1GC \                          # G1 GC 사용 (Java 9+ 기본)
  -Xms4g -Xmx4g \                         # 힙 크기 (최소=최대 권장)
  -XX:MaxGCPauseMillis=200 \              # 목표 일시 정지 시간 (기본 200ms)
  -XX:G1HeapRegionSize=8m \               # 리전 크기 (1~32MB, 2의 거듭제곱)
  -XX:InitiatingHeapOccupancyPercent=45 \ # IHOP: Concurrent Marking 시작 임계값
  -XX:G1ReservePercent=10 \               # 대피 실패 방지용 예약 공간
  -jar myapp.jar

시나리오별 튜닝

일시 정지 시간이 너무 길다

BASH
# 목표 일시 정지 시간 줄이기
-XX:MaxGCPauseMillis=100

# 너무 작게 설정하면 GC가 자주 발생하여 처리량이 떨어질 수 있음
# 50ms 이하는 신중하게 설정

Full GC가 발생한다

BASH
# IHOP를 낮춰서 Concurrent Marking을 더 일찍 시작
-XX:InitiatingHeapOccupancyPercent=35

# 힙 크기 증가 검토
-Xms8g -Xmx8g

# Concurrent Marking 스레드 증가
-XX:ConcGCThreads=4

Humongous 객체 문제

G1에서 리전 크기의 50% 이상인 객체는 Humongous 객체로 분류됩니다.

BASH
# 리전 크기를 늘려서 Humongous 할당 줄이기
-XX:G1HeapRegionSize=16m

# 또는 애플리케이션에서 대형 배열/객체 생성을 줄이기

to-space exhausted 발생

BASH
# 예약 공간 비율 증가
-XX:G1ReservePercent=15

# 힙 크기 증가
-Xmx8g

G1 GC 사이클 이해

PLAINTEXT
Young GC (Eden 가득참)
    ↓ (반복)
Concurrent Marking Cycle (IHOP 도달 시)
    1. Initial Mark (STW, Young GC와 동시)
    2. Root Region Scan
    3. Concurrent Mark (병행)
    4. Remark (STW)
    5. Cleanup (STW + 병행)

Mixed GC (Young + 가비지 비율 높은 Old 리전)
    ↓ (필요 시 반복)
다시 Young GC로

ZGC 튜닝

ZGC는 저지연(low-latency) GC로, 힙 크기에 관계없이 밀리초 이하의 일시 정지를 목표로 합니다.

기본 설정

BASH
java \
  -XX:+UseZGC \
  -XX:+ZGenerational \     # Java 21+: 세대별 ZGC (권장)
  -Xms4g -Xmx4g \
  -XX:SoftMaxHeapSize=3g \ # 힙 사용량 소프트 제한
  -jar myapp.jar

ZGC의 특성

특성
일시 정지 시간일반적으로 1ms 미만
힙 크기 지원수 MB ~ 16TB
메모리 오버헤드약 3~5% 추가
처리량G1 대비 약간 낮을 수 있음

ZGC 파라미터

BASH
# ZGC 동시성 스레드 수
-XX:ConcGCThreads=4

# 소프트 최대 힙 크기 — 이 크기 이하를 유지하도록 GC가 적극적으로 동작
-XX:SoftMaxHeapSize=3g

# 대형 페이지 사용 (성능 향상)
-XX:+UseLargePages

ZGC vs G1 선택 기준

기준G1ZGC
일시 정지 허용 범위수십~수백 ms수 ms 이하 필요
힙 크기수 GB수십 GB 이상
처리량 우선적합G1이 약간 유리할 수 있음
지연 시간 우선보통최적

GC 분석 도구

GCEasy (온라인)

GC 로그 파일을 업로드하면 자동으로 분석 리포트를 생성합니다.

  • 일시 정지 시간 분포
  • GC 원인 분석
  • 힙 사용량 추세
  • 튜닝 권장사항

GCViewer

BASH
# GC 로그를 시각화
java -jar gcviewer.jar gc.log

그래프로 힙 사용량 변화, GC 빈도, 일시 정지 시간 추이를 확인할 수 있습니다.

JDK Mission Control (JMC)

BASH
# JFR(Java Flight Recorder) 활성화
java -XX:StartFlightRecording=duration=60s,filename=recording.jfr \
     -jar myapp.jar

JFR은 GC뿐 아니라 스레드, I/O, 메서드 프로파일링까지 통합적으로 분석할 수 있습니다.

실전 튜닝 체크리스트

1단계: 측정

BASH
# GC 로그 활성화 (운영 환경에서도 항상 켜두기)
-Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=10,filesize=50M

2단계: 분석

확인할 항목:

  • **GC 빈도 **: Young GC가 초당 몇 번 발생하는가?
  • ** 일시 정지 시간 **: P99 기준으로 허용 범위 내인가?
  • **Full GC 유무 **: Full GC가 발생하면 원인 파악 필수
  • ** 힙 사용량 추세 **: GC 후에도 사용량이 계속 증가하면 메모리 누수 의심
  • Promotion rate: Young에서 Old로 승격되는 비율

3단계: 조정

BASH
# 일반적인 시작점
-Xms4g -Xmx4g                    # 힙 크기 고정
-XX:MaxGCPauseMillis=200          # 일시 정지 목표
-XX:+UseG1GC                      # 기본 GC

# 문제에 따라 하나씩 조정
# 변경 → 측정 → 분석 → 반복

4단계: 검증

  • 부하 테스트 환경에서 변경 효과 확인
  • 운영 배포 후 모니터링
  • GC 로그 지속 수집

주의할 점

Xms와 Xmx를 다르게 설정하면 안 된다

힙이 늘어나는 과정에서 GC가 자주 발생한다. 리사이징 자체도 비용이므로, 운영 환경에서는 같은 값으로 설정해야 한다.

파라미터를 한꺼번에 여러 개 바꾸면 원인 파악이 불가능하다

어떤 변경이 효과가 있었는지 알 수 없다. 반드시 하나씩 변경하고 측정해야 한다.

GC 로그를 끄면 문제가 터졌을 때 대응할 수 없다

"성능에 영향을 줄까 봐" GC 로그를 비활성화하는 경우가 있지만, GC 로그의 오버헤드는 무시할 수 있을 정도로 작다. 운영 환경에서도 항상 켜두는 것이 원칙이다.

힙만 키우고 원인을 찾지 않으면 시한폭탄이다

메모리 누수가 원인이면 힙을 아무리 키워도 결국 OOM이 발생한다. 힙 증가는 임시 조치일 뿐, 반드시 GC 로그와 힙 덤프로 근본 원인을 파악해야 한다.

정리

항목핵심
튜닝 첫 단계GC 로그 활성화 → 현재 상태 분석
G1 핵심 파라미터MaxGCPauseMillis(목표 pause), InitiatingHeapOccupancyPercent(IHOP)
ZGC 특성저지연(1ms 미만 pause), 대용량 힙에 적합, 설정이 단순
G1 vs ZGC 선택수십~수백 ms pause 허용 → G1, 수 ms 이하 필요 → ZGC
튜닝 원칙파라미터 하나씩 변경 → 측정 → 분석 사이클 반복
GC 로그운영 환경에서도 항상 활성화 (오버헤드 무시 가능)
분석 도구GCEasy(온라인), GCViewer(그래프), JFR+JMC(통합 프로파일링)
댓글 로딩 중...