지금까지 우리는 JVM 힙 메모리의 구조부터 ZGC와 같은 최신 가비지 컬렉터까지, 메모리 관리의 핵심 이론들을 깊이 있게 살펴보았습니다. 하지만 아무리 뛰어난 GC 알고리즘을 선택하더라도 완벽한 시스템은 없습니다. 실무에서 서버에 원인을 알 수 없는 지연(Latency) 현상이나 메모리 부족(OOM) 에러가 발생했을 때, 가장 먼저 열어봐야 할 ‘항공기의 블랙박스’가 바로 GC 로그(Log)입니다.
이번 포스팅에서는 Java 애플리케이션 운영 시 GC 로그를 남겨야 하는 이유와 필수 설정 방법, 그리고 추출된 로그를 분석하여 메모리 병목 현상을 진단하는 실전 노하우를 알아보겠습니다.
1. 필수 관문: GC 로그를 남기기 위한 JVM 옵션 설정
Java 애플리케이션은 기본적으로 상세한 GC 동작 내역을 파일로 남기지 않습니다. 따라서 운영 서버를 띄울 때는 반드시 GC 로그를 파일로 기록하도록 JVM 시작 옵션(Flag)을 추가해 주어야 합니다. Java 버전(9를 기준으로)에 따라 설정 방식이 완전히 달라지므로 주의해야 합니다.
- Java 8 이하 버전의 필수 옵션:
-Xloggc:/path/to/gc.log(로그 파일 경로 지정)-XX:+PrintGCDetails(GC 상세 정보 출력)-XX:+PrintGCDateStamps(GC 발생 시간 출력) - Java 9 이상 버전 (통합 로깅 시스템 도입): Java 9부터는 파편화되어 있던 로깅 옵션이 통합되었습니다.
-Xlog:gc*:/path/to/gc.log:time,uptime:filecount=10,filesize=50M(설명: gc와 관련된 모든(*) 로그를 남기며, 시간 정보를 포함하고, 파일 크기가 50MB가 넘으면 최대 10개까지 로테이션하여 저장하라는 매우 권장되는 실무 세팅입니다.)
2. GC 로그 구조 해석하기: 병목의 단서 찾기
옵션을 적용하고 애플리케이션을 구동하면 파일에 수많은 텍스트가 쌓이기 시작합니다. 가장 흔하게 볼 수 있는 Young Generation의 Minor GC 로그 한 줄을 해석해 보겠습니다.
[info][gc] GC(12) Pause Young (Normal) (G1 Evacuation Pause) 1024M->256M(2048M) 45.123ms
- Pause Young: Young 영역에서 발생한 Minor GC임을 의미합니다. (만약 이 자리에
Pause Full이 찍힌다면 심각한 성능 저하가 발생했음을 알 수 있습니다.) - G1 Evacuation Pause: G1 GC가 객체들을 다른 리전으로 대피(Evacuation)시키면서 멈춤(Pause) 현상이 발생했다는 뜻입니다.
- 1024M -> 256M (2048M): GC 발생 전 힙 사용량이 1024MB였고, 청소 후 256MB로 줄었으며, 전체 허용된 힙 사이즈는 2048MB라는 의미입니다.
- 45.123ms: 이 청소 작업을 위해 애플리케이션(Stop-The-World)이 약 45밀리초 동안 멈췄다는 의미입니다. 이 수치가 튀는 구간이 바로 병목 지점입니다.
3. 메모리 누수(Memory Leak)를 알려주는 치명적인 징후
GC 로그를 모니터링하다 보면 서버가 곧 죽을 것임을 암시하는 명확한 패턴이 있습니다.
정상적인 서버는 Full GC가 수행되고 나면 힙 사용량이 뚝 떨어져야 합니다. 하지만 메모리 누수(개발자의 코딩 실수로 객체가 해제되지 않음)가 발생한 서버는 Full GC가 끝난 후의 힙 사용량이 계속해서 우상향하는 패턴을 보입니다. 즉, (청소 후 용량)이 256M -> 512M -> 1024M 식으로 계속 증가하다가 결국 전체 힙 사이즈에 도달해버리고, 종국에는 애플리케이션이 OutOfMemoryError를 뱉으며 뻗어버리게 됩니다.
4. 실무자를 위한 시각화(Visualizer) 분석 도구 추천
수십만 줄에 달하는 텍스트 로그를 눈으로 분석하는 것은 비효율적입니다. 실무에서는 추출된 GC 로그 파일을 시각화해 주는 도구를 사용합니다.
- GCeasy (gceasy.io): 가장 추천하는 웹 기반 분석 툴입니다. GC 로그 파일을 업로드하기만 하면 머신러닝을 통해 메모리 누수 가능성, Stop-The-World 통계, 처리량(Throughput) 비율 등을 화려한 그래프와 함께 리포트로 제공해 줍니다.
- GCViewer: 외부망 통신이 불가능한 폐쇄망 서버 환경에서 분석해야 할 때 로컬 PC에 설치하여 사용하는 훌륭한 오픈소스 분석 도구입니다.
결론: 감에 의존하지 않는 데이터 기반의 튜닝
GC 튜닝은 “힙 사이즈를 늘리면 빨라지겠지”라는 감으로 접근해서는 안 됩니다. 반드시 GC 로그를 기록하고, 분석 툴을 통해 현재 시스템의 병목 원인이 너무 잦은 객체 생성 때문인지, 아니면 과도한 캐시 데이터 적재 때문인지를 정확히 파악한 후 JVM 옵션을 조정해야 합니다.
이것으로 엔터프라이즈 Java 성능 최적화의 첫 번째 큰 산인 ‘메모리와 GC’ 파트를 마칩니다. 다음 포스팅부터는 또 다른 성능 저하의 주범이자 멀티 코어 환경의 핵심인 ‘스레드(Thread) 및 동시성 제어’ 파트로 넘어가, ‘Java 스레드(Thread) 덤프 생성 방법과 상태(State) 분석 가이드’를 시작하겠습니다.