문제 발생

평소 100ms 안에 끝나던 요청이 간헐적으로 2초 이상 걸리고 같은 시각에 GC log가 보입니다. 시간대가 겹친다는 사실만으로 GC가 원인이라고 확정할 수는 없습니다. CPU 포화나 safepoint, 외부 I/O 대기도 같은 지연을 만들 수 있습니다.

pause 증가는 heap 크기 하나보다 live-set, allocation rate, collector와 concurrent phase 실패를 GC log·JFR로 확인해야 합니다.

원인 분석

세 값을 같은 시간축에 놓습니다. 첫째, 실제 pause 시간과 원인입니다. 둘째, 단위 시간당 새 객체가 만들어지는 allocation rate입니다. 셋째, GC 뒤에도 남는 live set입니다. allocation rate만 높고 live set이 안정적이면 임시 객체가 많을 가능성이 있고, live set이 계속 증가하면 오래 보유되는 참조를 더 살펴야 합니다. heap 사용률 한 장이나 평균 latency만으로 둘을 구분할 수 없습니다.

java -Xlog:gc*:file=gc.log:time,uptime,level,tags \
  -XX:StartFlightRecording=filename=app.jfr,dumponexit=true \
  -jar app.jar

해결 방안

JFR과 GC log에서 pause가 실제 느린 요청과 일치하는지 먼저 확인합니다. allocation이 원인이면 반복문 안 임시 collection, 큰 문자열·배열 복사처럼 가장 큰 할당 지점을 줄입니다. live set 증가라면 class histogram과 heap dump의 GC root 보유 경로를 확인합니다. collector나 -Xmx 변경은 같은 workload에서 p95·p99, 처리량, pause, live set과 전체 RSS가 함께 나아질 때만 채택합니다.

공식 문서

JDK version, collector, container memory limit과 실제 workload에서 다시 측정합니다.