[JAVA] GC 로그 분석으로 성능 문제 해결한 과정

운영 중이던 Java 서비스에서 응답 지연이 간헐적으로 발생했습니다. 단순히 GC가 많다는 느낌만으로는 원인을 잡기 어려웠고, 결국 GC 로그를 기반으로 흐름을 하나씩 따라가며 문제를 정리했던 경험을 공유해보겠습니다.

Java GC 로그 분석으로 성능 문제 원인 찾기

Java GC 로그 분석은 메모리 문제를 단순 감이 아니라 근거로 파악하기 위한 가장 기본적인 접근입니다. 특히 G1GC를 사용하는 환경에서는 로그만 잘 읽어도 메모리 압박인지, GC 튜닝 문제인지 방향을 빠르게 잡을 수 있습니다.

 

문제 상황과 초기 증상

서비스는 평소와 동일한 트래픽에서 간헐적으로 응답 시간이 튀는 현상이 있었습니다. CPU나 네트워크 문제는 보이지 않았고, 특정 시점에만 응답이 늦어지는 패턴이 반복되었습니다.

이런 경우 보통 Full GC를 의심하게 되는데, 실제로 로그를 확인하기 전까지는 확신하기 어렵습니다. 그래서 GC 로그부터 수집해서 흐름을 보기 시작했습니다.

 

GC 로그 수집 설정

먼저 GC 로그가 제대로 남도록 JVM 옵션을 확인합니다. 기본적으로 아래 옵션 정도는 설정해 두는 것이 좋습니다.


-XX:+UseG1GC
-Xlog:gc*:file=gc.log:time,uptime,level,tags

G1GC 기준으로는 Unified Logging을 사용하는 것이 분석하기 편합니다. 시간 정보와 함께 로그가 찍히는지도 꼭 확인해야 합니다.

 

로그에서 먼저 확인한 것

GC 로그를 보면 가장 먼저 확인해야 할 것은 두 가지입니다.

1. GC 발생 주기

Young GC가 너무 자주 발생하는지, 아니면 Full GC가 발생하는지를 먼저 봅니다. 이번 케이스에서는 Young GC는 정상 범위였지만, 특정 시점에 Full GC가 발생하고 있었습니다.

2. GC pause 시간

응답 지연이 발생한 시점과 GC pause 시간이 겹치는지 확인합니다. 실제로 해당 시점에 수 초 단위의 pause가 확인되었습니다.

 

원인 추적 과정

Full GC가 발생하는 이유를 찾기 위해 Old 영역 사용량 변화를 집중적으로 봤습니다.

로그를 보면 GC 이후에도 Old 영역이 거의 줄어들지 않는 패턴이 반복되었습니다. 이 경우는 단순 GC 부족이 아니라, 객체가 계속 살아남고 있다는 의미입니다.


[GC pause (G1 Evacuation Pause) ...]
[Full GC (Allocation Failure) ...]

여기서 Allocation Failure가 반복되는 것을 확인할 수 있었습니다. 즉, 새로운 객체를 할당할 공간이 부족해서 Full GC가 발생하고 있던 상황입니다.

이 단계에서는 보통 두 가지로 나뉩니다.

  • 메모리 자체가 부족한 경우
  • 객체가 오래 살아남는 구조인 경우

 

실제 원인

Heap dump와 함께 확인해보니, 특정 캐시 구조에서 객체가 의도보다 오래 유지되고 있었습니다.

코드 상에서는 만료 처리가 있다고 생각했지만, 실제로는 참조가 남아 있어서 GC 대상이 되지 않는 상태였습니다.

이 부분은 코드만 보면 바로 보이지 않는 경우가 많습니다. GC 로그에서 Old 영역이 줄지 않는 패턴이 보일 때 이런 구조를 의심하는 것이 좋습니다.

 

해결 방법

해결은 크게 두 가지 방향으로 진행했습니다.

1. 캐시 구조 개선

불필요하게 오래 유지되는 객체를 제거하고, 명시적으로 만료되도록 구조를 수정했습니다.

2. GC 튜닝 최소 적용

구조 개선 이후에도 약간의 여유를 두기 위해 Heap 사이즈를 조정했습니다. 다만 GC 튜닝으로 문제를 덮는 방식은 지양하는 편입니다.

원인이 코드 구조라면, 먼저 구조를 고치는 것이 유지보수 관점에서도 더 안정적입니다.

 

분석하면서 중요했던 포인트

이번 케이스에서 정리해보면 다음 세 가지가 핵심이었습니다.

  • GC 로그는 반드시 시간 기준으로 본다
  • Old 영역 감소 여부가 중요하다
  • Full GC 자체보다 발생 이유를 먼저 본다

실무에서는 GC가 많다는 사실보다, 왜 그 GC가 발생하는지를 파악하는 것이 더 중요합니다.

 

마무리 정리

GC 로그 분석은 어렵게 느껴질 수 있지만, 몇 가지 기준만 잡으면 흐름이 보이기 시작합니다.

특히 아래 순서로 접근하면 대부분 방향을 잡을 수 있습니다.

  • GC 종류 확인 (Young / Full)
  • Pause 시간 확인
  • Old 영역 변화 확인
  • Allocation Failure 여부 확인

이 과정을 반복하다 보면, 단순 튜닝이 아니라 구조적인 문제까지 자연스럽게 보이게 됩니다.

로그를 읽는다는 것은 단순히 숫자를 보는 것이 아니라, 시스템이 어떻게 메모리를 쓰고 있는지를 이해하는 과정이라고 보면 됩니다.