JVM GC 튜닝 시작 전 필수 준비: 모니터링 활성화와 튜닝 필요성 판단
대규모 백엔드 시스템을 운영하다 보면 무턱대고 JVM Heap 옵션(-Xms, -Xmx)부터 건드리고 보는 이른바 ‘묻지마 튜닝’ 의 유혹에 빠지기 쉽습니다. 사내 빅데이터 모니터링 플랫폼의 대용량 데이터 콜렉터 데몬을 개발할 때, 저희는 튜닝에 돌입하기 전 “지금 우리 시스템이 정말 튜닝이 필요한 상태인가?” 를 철저히 검증하는 사전 단계를 거쳤습니다.
튜닝은 완벽히 동작하는 코드를 망가뜨릴 위험을 내포하고 있어, 객관적인 모니터링 데이터를 바탕으로 결정을 내려야 합니다.
1. GC 튜닝이 (아직) 필요 없는 조건
로그와 프로파일링 도구를 붙여 며칠간 관찰했을 때, 아래 지표 안에 들어온다면 GC 튜닝보다 인프라 스케일 아웃이나 로직 개선을 먼저 고민하는 것이 좋습니다.
| 지표 | 양호한 기준 | 위험 신호 |
|---|---|---|
| Minor GC 처리 시간 | 평균 50ms 이내 | 100ms 초과 |
| Minor GC 발생 주기 | 10초 이상 | 2~3초 이하 |
| Full GC 처리 시간 | 1초 이내 | 수 초 이상 (STW 체감) |
| Full GC 발생 주기 | 10분에 1회 미만 | 수 분마다 발생 |
| Heap 사용률 (Old Gen) | 80% 이하 안정 유지 | 지속적으로 90%↑ |
이 기준을 벗어나서 STW(Stop-The-World) 현상 때문에 장애 알람이 계속 울린다면 GC 튜닝 실전 단계로 진입할 차례입니다.
2. jstat으로 현재 GC 상태 진단
튜닝 전 가장 빠르게 현황을 파악하는 방법은 jstat입니다.
# PID 확인
$ jps -l
# 1초(1000ms) 간격으로 GC 통계 출력
$ jstat -gcutil <PID> 1000 S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 47.02 83.14 32.01 95.72 92.11 152 6.343 3 1.512 7.855| 컬럼 | 의미 | 체크 포인트 |
|---|---|---|
E | Eden 영역 사용률(%) | 100%에 닿으면 Minor GC 발생 |
O | Old 영역 사용률(%) | 지속 상승 → 메모리 릭 의심 |
YGC / YGCT | Minor GC 횟수 / 총 소요 시간 | YGCT/YGC 로 평균 시간 계산 |
FGC / FGCT | Full GC 횟수 / 총 소요 시간 | FGC가 자주 올라가면 위험 |
3. 모니터링 도구 활성화 설정
① jstatd 원격 접속 허용
GUI 모니터링 툴(VisualVM 등)이 에이전트 없이 JVM 힙 상태를 원격으로 가져갈 수 있도록 tools.policy 파일을 설정합니다.
$ vi /tmp/tools.policygrant codebase "file:${java.home}/../lib/tools.jar" {
permission java.security.AllPermission;
};# jstatd 백그라운드 실행
$ jstatd -J-Djava.security.policy=/tmp/tools.policy &② Tomcat setenv.sh: JMX 및 GC 로그 활성화
catalina.sh 또는 setenv.sh에 추가하여 JVM 구동과 동시에 내부 모니터링 포트(9898)를 열고 이벤트 단위로 GC 로그를 파일에 기록합니다.
# JMX 원격 접속 허용 (VisualVM 연결용)
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9898
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
# GC 로그 파일 기록 (GCEasy 등 분석 도구의 입력값)
-verbose:gc
-Xloggc:../logs/gc.log
-XX:+PrintGCDateStamps
-XX:+PrintGCDetails
# GC 로그 로테이션 (로그 무한 증가 방지)
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=5
-XX:GCLogFileSize=20m
# OOM 발생 시 Heap Dump 자동 생성
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=../logs/heap-dump.hprof[실무 팁] STW로 갑자기 서버가 내려갔을 때, 뒤늦게 “아 GC 로그 켜둘걸!” 하고 후회하는 상황이 자주 있습니다. 운영 서버에 올리기 전, 기본 템플릿으로 위 옵션들을 반드시 심어두는 것을 강력히 권장합니다.
4. GC 로그 파일 읽기
활성화한 GC 로그는 아래 형태로 쌓입니다. 패턴을 익혀두면 눈으로 대략적인 이상 상황을 감지할 수 있습니다.
2018-10-11T14:23:01.452+0900: 52.123: [GC (Allocation Failure)
[PSYoungGen: 131072K->12345K(153600K)]
143210K->24511K(503808K), 0.0231450 secs]
[Times: user=0.09 sys=0.01, real=0.02 secs]| 항목 | 의미 |
|---|---|
Allocation Failure | Eden이 꽉 찼을 때 Minor GC 발생(정상) |
PSYoungGen: 131072K→12345K | Minor GC 전후 Young 영역 사용량 |
0.0231 secs | 이번 GC에 걸린 STW 시간 |
user=0.09 | GC 스레드가 실제 CPU를 쓴 시간 |
[위험 신호 패턴]
[Full GC (Ergonomics) ...] 2.345 secs ← 2초 넘는 Full GC
[Full GC (System.gc()) ...] ← 코드에서 강제 GC 호출 (안티패턴!)
[GC (GCLocker Initiated GC) ...] ← JNI 코드가 GC를 막고 있음5. GCEasy로 자동 분석
GC 로그 파일을 GCEasy 에 업로드하면 처리량(Throughput), 최대 STW 시간, 메모리 효율 등을 시각화된 리포트로 받아볼 수 있습니다. 튜닝 전/후 로그를 각각 올려 효과를 수치로 비교하는 데 특히 유용합니다.