4. 응용 변형 예제
변형 1: 운영 JVM 표준 옵션
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.jarExitOnOutOfMemoryError 는 OOM 뒤 반쯤 죽은 채로 버티지 말고 바로 종료해 컨테이너가 재시작하게 합니다. 힙 덤프를 받은 다음 종료되므로 순서는 문제없습니다.
변형 2: 컨테이너에서 힙 크기
-Xmx 를 고정하는 대신 비율로 줍니다. 컨테이너 메모리 한도가 바뀌어도 옵션을 안 고쳐도 됩니다.
-XX:MaxRAMPercentage=70.0 -XX:InitialRAMPercentage=70.0JVM 은 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()로 흘립니다.