홈 › 고급 › 10 / 10

JVM 메모리·GC·OOM 진단

힙은 어떻게 생겼고, 무엇이 남고, 터지면 어디를 보는가
섹션 7진행 0 / 10

4. 응용 변형 예제

변형 1: 운영 JVM 표준 옵션

text
java -Xms2g -Xmx2g \
     -XX:+UseG1GC -XX:MaxGCPauseMillis=200 \
     -Xlog:gc*:file=/var/log/app/gc.log:time,uptime:filecount=5,filesize=20m \
     -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/app/ \
     -XX:+ExitOnOutOfMemoryError \
     -jar app.jar

ExitOnOutOfMemoryError 는 OOM 뒤 반쯤 죽은 채로 버티지 말고 바로 종료해 컨테이너가 재시작하게 합니다. 힙 덤프를 받은 다음 종료되므로 순서는 문제없습니다.

변형 2: 컨테이너에서 힙 크기

-Xmx 를 고정하는 대신 비율로 줍니다. 컨테이너 메모리 한도가 바뀌어도 옵션을 안 고쳐도 됩니다.

text
-XX:MaxRAMPercentage=70.0 -XX:InitialRAMPercentage=70.0

JVM 은 cgroup 한도를 읽어 그 70% 를 힙으로 씁니다. 나머지 30% 가 메타스페이스·스택·네이티브 메모리 몫입니다.

변형 3: 살아 있는 프로세스 진단 명령

명령 보는 것
jcmd <pid> GC.heap_info 세대별 사용량 요약
jcmd <pid> GC.class_histogram 클래스별 객체 수·크기 상위
jcmd <pid> GC.heap_dump f.hprof 힙 덤프
jcmd <pid> Thread.print 스레드 덤프 (데드락 확인)
jstat -gcutil <pid> 1000 1초마다 GC 통계

GC.class_histogram 이 가장 가볍습니다. 덤프를 받기 전에 이것으로 "어떤 클래스가 몇 MB" 를 먼저 봅니다. byte[] 와 String 이 상위인 것은 정상이고, 업무 클래스가 수십만 개면 그 클래스를 잡는 컬렉션을 찾습니다.

변형 4: 배치에서 메모리 절약

100만 건 정산 배치가 OOM 을 낸다면 대개 결과 전체를 리스트에 모으는 구조입니다. 청크 단위로 읽고 쓰는 배치 레슨의 구조가 답이고, 그 안에서도 다음을 지킵니다.

  • ID 목록은 long[] 또는 LongStream 으로 듭니다.
  • 조회 결과는 Map<String, Object> 대신 record 로 받습니다. 필드 이름 문자열이 행마다 반복되지 않습니다.
  • 파일은 한 번에 읽지 말고 BufferedReader.lines() 로 흘립니다.