스프링 부트 같은 자바 애플리케이션을 운영하다 보면 “왜 느려졌지?”라는 순간이 반드시 온다.
이때 가장 먼저 확인할 수 있는 저비용 도구가 바로 jstat이다.
이 글은 jstat을 바로 써먹을 수 있는 수준으로 정리했다. 설치 방법 설명 대신, “어떤 옵션으로 찍고, 각 헤더가 무엇을 의미하며, 무엇을 보면 이상 신호인지”에 집중한다.
1. jstat 한 줄 요약
- 무중단으로 JVM 내부 메모리 지표를 뽑는 도구
- GC(Young/Old), Metaspace, 클래스 로딩, 컴파일러 상태 등을 주기적으로 관찰 가능
- jstat -<옵션> <pid> [간격ms] [횟수]
예)
jstat -gcutil <pid> 1000
→ 1초마다 힙/메타스페이스 사용률과 GC 횟수/시간을 요약해 보여준다.
2. 가장 많이 쓰는 옵션 3종
2.1 -gcutil : 사용률(%)과 GC 누적 요약
가장 먼저 외워야 할 명령이다.
jstat -gcutil <pid> 1000
출력 예:
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 32.00 12.34 60.45 85.12 75.00 123 1.234 5 0.678 1.912
헤더 의미:
- S0/S1: Survivor 0/1 사용률(%). 둘 중 하나는 비어 있거나, G1에선 시점에 따라 용량 0 → - 표기 가능.
- E: Eden 사용률(%). 새 객체가 먼저 쌓이는 곳.
- O: Old 사용률(%). 오래 사는 객체가 있는 곳.
- M: Metaspace 사용률(%). 클래스 메타데이터 저장소.
- CCS: Compressed Class Space 사용률(%). 클래스 포인터 테이블.
- YGC/YGCT: Young GC 횟수/총 시간(초).
- FGC/FGCT: Full GC 횟수/총 시간(초).
- GCT: 전체 GC 시간(초) = YGCT + FGCT.
해석 팁:
- O가 80~90% 이상으로 꾸준히 높으면 Old 압박 의심.
- **M/CCS가 95%~99%**라면 Metadata GC Threshold로 Full GC 임박.
- FGC/FGCT가 빠르게 증가하면 체감 지연 위험. 원인(reason)은 gc.log로 교차 확인.
2.2 -gc : 절대치(bytes) 기반의 상세
사용률 말고 절대 크기가 필요할 때.
jstat -gc <pid> 1000
대표 헤더:
- S0C/S1C/EC/OC/MC/CCSC: 각 영역의 용량(Capacity)
- S0U/S1U/EU/OU/MU/CCSU: 각 영역의 사용량(Used)
- YGC/YGCT/FGC/FGCT/GCT: 위와 동일
해석 팁:
- 용량과 사용량을 함께 보면 실제 바이트 단위의 증가/감소를 읽을 수 있다.
- 특정 시점 이후 **MU(메타스페이스 사용량)**가 계속 증가하면 클래스 로딩 누수 가능성.
2.3 -gccapacity : 영역별 용량 변화 추적
GC 전후로 영역 용량이 어떻게 조정되는지 본다.
jstat -gccapacity <pid> 1000
- G1GC처럼 Region 기반인 수집기에서는 용량이 동적으로 변한다.
- **서바이버 목표 조정(tenuring/TargetSurvivorRatio)**이 의도대로 먹는지 관찰할 때 유용.
3. 자주 묻는 해석 포인트
3.1 S0이 -로 나오는 이유
Survivor는 두 개(S0, S1)가 번갈아(from/to) 쓰인다. G1처럼 동적 할당인 경우 특정 순간 S0 용량이 0일 수 있고, 그러면 사용률을 계산할 수 없어 -로 표시된다. 비정상 아니다.
3.2 Metaspace가 높을 때
M이 99% 근처, CCS도 95% 이상이면 클래스 메타데이터가 한계치다.
- 원인 유형:
- ClassLoader 누수
- 동적 클래스 생성 과다(프록시/리플렉션/스크립트 엔진)
- MaxMetaspaceSize가 너무 작음
- 결과: Metadata GC Threshold로 Full GC 발생.
- 조치: jcmd <pid> VM.classloaders, jcmd <pid> GC.class_stats, jstat -class <pid> 1000으로 증가 추세/로더별 점유 확인. 임시로 -XX:MaxMetaspaceSize 상향.
3.3 Young이 자주 터질 때
- YGC가 빨리 오르면 생성/일시 객체가 많다는 뜻.
- Eden(E) 사용률이 요동치고, S1이 자주 80~100%라면 생존율이 높아 승격 압력이 커질 수 있다. Old(O) 상승 속도를 함께 보라.
3.4 Full GC가 많은가?
정상 아님. 분·초 단위로 반복되면 거의 항상 문제다.
- Metaspace 이슈 → 위와 동일.
- Old 압박/단편화 → 직전에 to-space exhausted, evacuation failure 같은 신호가 gc.log에 찍힌다.
4. 실전 루틴: 10분 컷 점검
- 요약으로 전체 감(感)
jstat -gcutil <pid> 1000 10
- O, M, CCS, FGC/FGCT 추세를 10초만 관찰해도 대략의 체온이 나온다.
- 절대치로 크기 판단
jstat -gc <pid> 1000 5
- MU(메타스페이스 사용량), OU(Old 사용량) 추세 확인.
- 클래스 로딩 추세
jstat -class <pid> 1000 30
- Loaded가 계속 증가하면 누수 의심.
- 필요 시 상세 조사
jcmd <pid> VM.classloaders
jcmd <pid> GC.class_stats
- 로더별 점유와 메타데이터 통계.
- gc.log와 교차 확인
- 트리거가 Metadata GC Threshold인지, to-space exhausted인지, Allocation Failure인지 확인.
5. 알람 기준(권장 초안)
- Full GC가 10분에 1회 초과 또는 FGCT가 분당 수백 ms 이상 → 경고
- M, CCS가 95% 이상 1분 지속 → 경고 (Metaspace 임계치 근접)
- O가 90% 이상 5분 지속 → 경고 (Old 압박)
- YGC 급증 + O 동반 상승 → 주간 추세 경고 (승격 압력)
지표 수집은 jstat만으로도 시작할 수 있지만, 장기적으로는 JMX Exporter로 Prometheus/Grafana에 넣는 것이 좋다.
6. 컨테이너/리눅스 운영 팁
- pid 찾기
- jps -lv # 또는 ps -ef | grep java
- 권한 문제로 jstat가 안 보이면 동일 사용자로 접속하거나 sudo -E로 JAVA_HOME/JDK 툴 접근 권한을 맞춘다.
- 컨테이너에서는 JDK 도구가 이미지에 없을 수 있다.
- 런타임 이미지는 JRE만 있을 때가 많으니, 사이드카나 디버그 이미지를 준비한다.
- 자바 8 구버전은 cgroup 인식이 미흡할 수 있다. 컨테이너 메모리/CPU 제한이 GC에 반영되는지 확인.
7. jstat 치트시트
# 1초 간격, 무한 출력
jstat -gcutil <pid> 1000
# 1초 간격, 60회
jstat -gcutil <pid> 1000 60
# 절대치 보기
jstat -gc <pid> 1000
# 영역 용량 추적
jstat -gccapacity <pid> 2000
# 클래스 로딩 추이
jstat -class <pid> 1000
# 컴파일러 통계(JIT)
jstat -compiler <pid> 1000
# 헤더 포함
jstat -gcutil -h3 <pid> 1000
CSV로 저장(간단 버전):
jstat -gcutil <pid> 1000 | awk 'BEGIN{OFS=","} NR==1{print; next} {print}' >> gcutil.csv
8. 무엇을 보면 “바로 조치”할 것인가
- M/CCS ≥ 95% 지속
→ Metaspace 원인 Full GC 임박.
→ jcmd VM.classloaders, GC.class_stats로 로더별 점유 확인, 임시로 -XX:MaxMetaspaceSize 상향. - O ≥ 90% 지속 + YGC 급증
→ 승격 압력/Old 압박.
→ 큰 객체 패턴/버퍼 복사 과다 점검, G1이라면 G1ReservePercent, InitiatingHeapOccupancyPercent 검토. - FGC 증가, FGCT 큰 폭 상승
→ 즉시 gc.log로 트리거 확인.
→ Metaspace면 로더/동적 클래스, Old면 승격 실패/단편화 라인.
9. 마무리
jstat은 “현장에서 바로 쓸 수 있는 최소한의 계기판”이다.
-gcutil로 큰 흐름을 잡고, -gc/-gccapacity/-class로 원인을 좁힌 뒤, gc.log와 교차 확인하면 추측이 아닌 증거 기반으로 조치할 수 있다.
특히 **Metaspace(M/CCS)**와 Old(O), 그리고 FGC/FGCT의 추세를 습관적으로 보는 것만으로도 대부분의 장애 전조를 조기 포착할 수 있다.
'Back-End' 카테고리의 다른 글
| AES-256-GCM 암호화 방식 정리 (0) | 2026.03.17 |
|---|---|
| 금융기관 API에서 사용하는 Signed JWT + RFC8785 메시지 서명 구조 정리 (0) | 2026.02.25 |
| 웹소켓 정리 2 (0) | 2025.05.07 |
| 웹소켓 정리 (1) | 2025.05.06 |
| 실시간 데이터 처리를 위한 RabbitMQ 설계와 웹소켓 연동 (1) | 2025.05.02 |