트러블슈팅 / JVM OutOfMemoryError·OS OOM 장애 환경 Linux systemd · HotSpot/OpenJDK 17·21·25 · 서비스 계정으로 attach 가능한 JDK diagnostic tool
분류 CPU·메모리
검토일 2026-09-04
진행 5단계 · 조회 우선
! 운영 서버에서는 출력과 대상을 확인한 뒤 다음 단계로 이동하세요 명령의 SERVICE_NAME, PID_NUMBER, 도메인과 경로는 예시입니다. 실제 값으로 바꾸기 전 대상 서버·권한·영향 범위를 확인하고, 삭제나 전체 권한 부여 명령은 임의로 추가하지 마세요.
SAFE OPERATING BOUNDARY
중단·복구 기준부터 확인하세요 STOP CONDITIONS 여기서는 멈추세요 서비스 계정·정확한 PID·같은 JDK major diagnostic tool을 확정하지 못했거나 현재 부하가 높은 경우 histogram·thread dump를 반복 실행하지 않습니다. heap dump·thread dump·JFR 저장공간과 0600 권한·민감정보 처리 승인이 없으면 새 dump를 생성하지 않습니다. DB migration·queue 처리 중인 서비스의 재시작과 retry·중복 처리 영향을 확인하지 못했다면 변경 단계로 이동하지 않습니다. ROLLBACK 복구 기준 백업한 JVM option 파일과 이전 정상 code release를 같은 묶음으로 복원하고 unit을 검증한 뒤 한 번만 재시작합니다. heap dump·GC log·JFR은 원인 분석 전 삭제하지 않고 제한된 경로로 격리합니다. Xmx·MemoryMax 변경만 되돌려도 DB·queue 작업 중복이나 이미 손상된 in-memory state는 복원되지 않으므로 업무별 보정 절차를 따릅니다.
ESCALATION PACK 담당자에게 전달할 자료 장애 시각의 Java OOM detail message·stack trace 요약과 kernel/cgroup OOM 기록 JVM version·VM.flags·GC.heap_info·RSS·MemoryMax·thread 수와 restart count 권한 제한된 histogram·JFR·heap dump checksum과 변경 전후 GC·오류율·지연 지표 2026년 9월 4일 기준 공식 upstream 문서와 현재 지원 명령을 대조했습니다. 먼저 최소 범위 읽기 전용 조회로 사실을 확인하고, 변경은 영향·백업·복구 경로와 담당자 승인을 확보한 대상에만 적용합니다. <...> 자리표시자는 승인된 리터럴 값으로 직접 치환하며 외부 입력으로 shell 명령을 조립하거나 eval하지 않습니다. BEFORE YOU START
이런 증상에서 시작합니다 로그에 java.lang.OutOfMemoryError: Java heap space가 남음 Metaspace 또는 Direct buffer memory 오류 뒤 서비스가 재시작됨 JVM 오류 없이 journal kernel 로그에 Out of memory: Killed process가 남음 Full GC와 응답 지연이 반복되고 RSS가 계속 증가함 CHECK THE BRANCH
놓치기 쉬운 원인 분기 01 Java heap space·GC overhead limit인 경우 heap 최대값이 workload의 정상 peak보다 작은지, GC 후에도 특정 class instance가 누적되는지 구분합니다. Xmx 증가만으로 누수를 숨기지 않습니다.
02 Metaspace·classloader 관련인 경우 동적 class 생성과 반복 배포에서 classloader가 회수되지 않는지 확인합니다. heap histogram만으로 native Metaspace 크기를 단정하지 않습니다.
03 Direct buffer·native thread·unable to create native thread인 경우 heap 밖 native memory, thread 수, virtual memory와 process·cgroup 한계를 확인합니다. host available memory를 무시한 Xmx 증가는 native OOM을 악화시킵니다.
04 Linux OOM killer 또는 container limit인 경우 Java exception 없이 SIGKILL·exit 137이면 kernel/cgroup 로그와 MemoryMax·container limit을 우선 확인합니다. JVM heap dump가 생성되지 않을 수 있습니다.
1 장애 시각·종료 이유·kernel OOM 구분 2 JVM flags·heap·host memory 기준선 확인 3 class·thread·GC 추세와 dump 가능성 확인 4 JVM 옵션·service limit·dump 경로 검토 5 진단 보강 또는 용량·코드 수정 후 검증 서비스 상태·재시작 명령어 복사
systemctl status <JAVA_SERVICE>.service --no-pager -l
systemctl show <JAVA_SERVICE>.service -p MainPID -p NRestarts -p ExecMainStatus -p ResultJava OOM 오류 주변 명령어 복사
journalctl -u <JAVA_SERVICE>.service --since '<INCIDENT_START>' --until '<INCIDENT_END>' --no-pager | grep -E -C 5 'OutOfMemoryError|Java heap space|Metaspace|Direct buffer memory|unable to create native thread|Killed'stack trace·요청값에는 개인정보와 내부 식별자가 포함될 수 있어 공유 전 마스킹합니다. kernel·systemd OOM 명령어 복사
journalctl -k --since '<INCIDENT_START>' --until '<INCIDENT_END>' --no-pager | grep -Ei -C 4 'out of memory|oom-kill|killed process'cgroup memory 한계 명령어 복사
systemctl show <JAVA_SERVICE>.service -p MemoryCurrent -p MemoryPeak -p MemoryMax -p OOMPolicy결과 읽기 Java stack trace가 있으면 detail message로 memory 영역을 분류하고, kernel kill만 있으면 host/cgroup 압박을 우선 봅니다. 두 현상이 연쇄적으로 발생할 수도 있습니다.
다음 판단 현재 JVM이 살아 있다면 PID·flags·heap과 host memory 기준선을 수집합니다.
Java process 명령어 복사
ps -o pid,ppid,user,%cpu,%mem,rss,vsz,etime,stat,comm -p <JAVA_PID>JVM flags 명령어 복사
sudo -u <JAVA_SERVICE_USER> <MATCHING_JDK_HOME>/bin/jcmd <JAVA_PID> VM.flagsheap 정보 명령어 복사
sudo -u <JAVA_SERVICE_USER> <MATCHING_JDK_HOME>/bin/jcmd <JAVA_PID> GC.heap_infohost memory·pressure 명령어 복사
free -h
cat /proc/pressure/memory
ps -eo pid,user,rss,vsz,%mem,comm --sort=-rss | head -20결과 읽기 Xmx와 실제 RSS 차이는 Metaspace·code cache·thread stack·direct buffer·native library·mapped file 등이 포함될 수 있습니다. RSS 하나만으로 leak을 확정하지 않습니다.
다음 판단 낮은 영향의 histogram·thread·GC 추세를 확보할지 장애 심각도와 pause 허용 범위로 판단합니다.
class histogram 1회 명령어 복사
sudo -u <JAVA_SERVICE_USER> <MATCHING_JDK_HOME>/bin/jcmd <JAVA_PID> GC.class_histogram부하가 높거나 heap이 큰 운영 JVM에서는 성능 영향 승인을 받고 한 번만 실행합니다. 결과에는 업무 class 이름이 포함됩니다. thread 수·상태 명령어 복사
ps -L -p <JAVA_PID> -o pid,tid,psr,pcpu,stat,comm | head -200
sudo -u <JAVA_SERVICE_USER> <MATCHING_JDK_HOME>/bin/jcmd <JAVA_PID> Thread.print -lthread dump에는 SQL·URL·사용자 데이터가 있을 수 있어 0600 경로에서 마스킹 후 공유합니다. 기존 JFR 상태 명령어 복사
sudo -u <JAVA_SERVICE_USER> <MATCHING_JDK_HOME>/bin/jcmd <JAVA_PID> JFR.check기존 dump·GC log와 공간 명령어 복사
sudo find <APPROVED_JVM_DIAGNOSTIC_DIR> -maxdepth 1 -type f -printf '%m %u:%g %s %TY-%Tm-%TdT%TH:%TM:%TS %p\n'
df -hT <APPROVED_JVM_DIAGNOSTIC_DIR>결과 읽기 시간을 두고 얻은 histogram·JFR에서 특정 class가 지속 성장할 때 leak 후보가 됩니다. 한 번의 상위 class 목록만으로 삭제·재시작·용량 증가를 결정하지 않습니다.
다음 판단 heap·native·thread·host 중 원인 영역과 dump 크기·민감도·쓰기 권한을 확정합니다.
unit·drop-in 경로 명령어 복사
systemctl show <JAVA_SERVICE>.service -p FragmentPath -p DropInPaths -p User -p Group -p LimitNOFILE -p TasksMax -p MemoryMax진단 경로 권한 명령어 복사
sudo stat -Lc '%a %U:%G %n' <APPROVED_JVM_DIAGNOSTIC_DIR>
sudo -u <JAVA_SERVICE_USER> test -w <APPROVED_JVM_DIAGNOSTIC_DIR> && echo writabledisk 예상 여유 명령어 복사
df -hT <APPROVED_JVM_DIAGNOSTIC_DIR>
sudo du -sh <APPROVED_JVM_DIAGNOSTIC_DIR>서비스 로그 마지막 오류 명령어 복사
journalctl -u <JAVA_SERVICE>.service -n 200 --no-pager환경·요청·개인정보를 포함할 수 있어 원문을 외부 티켓에 붙여넣지 않습니다. 결과 읽기 HeapDumpPath는 최소 Xmx에 가까운 파일과 임시 쓰기 여유를 감당해야 하며 서비스 계정만 쓸 수 있어야 합니다. host memory는 Xmx보다 충분히 커야 합니다.
다음 판단 원인에 맞는 코드·limit·JVM 옵션 중 하나의 최소 변경과 되돌릴 파일을 승인합니다.
변경 단계입니다. 실행 전 대상 이름과 경로, 서비스 중단 영향, 복구 방법을 다시 확인하세요.
JVM 옵션 파일 백업·편집 명령어 복사
install -d -m 0700 <APPROVED_BACKUP_DIR>
umask 077
sudo cp --archive <JVM_OPTIONS_FILE> <APPROVED_BACKUP_DIR>/jvm-options.before
sudo chmod 0600 <APPROVED_BACKUP_DIR>/jvm-options.before
sudoedit <JVM_OPTIONS_FILE>예: -XX:+HeapDumpOnOutOfMemoryError와 -XX:HeapDumpPath=<APPROVED_JVM_DIAGNOSTIC_DIR>를 검토합니다. Xmx는 host·cgroup·native 여유를 계산한 승인값만 사용합니다. 옵션·unit 검증 명령어 복사
sudo -u <JAVA_SERVICE_USER> <MATCHING_JDK_HOME>/bin/java -version
sudo systemd-analyze verify <JAVA_SERVICE_UNIT_FILE>JVM binary와 unit 문법만 확인하며 애플리케이션을 별도로 시작하지 않습니다. 승인된 한 번의 재시작 명령어 복사
sudo systemctl restart <JAVA_SERVICE>.service
systemctl status <JAVA_SERVICE>.service --no-pager -l새 PID·flags·health 명령어 복사
systemctl show <JAVA_SERVICE>.service -p MainPID -p NRestarts -p MemoryCurrent -p MemoryPeak
sudo -u <JAVA_SERVICE_USER> <MATCHING_JDK_HOME>/bin/jcmd <NEW_JAVA_PID> VM.flags
curl -fsS http://127.0.0.1:<APP_PORT>/<HEALTH_PATH>결과 읽기 서비스 health·오류율이 회복되고 RSS·GC pause·allocation rate·restart count가 정상 범위여야 합니다. 옵션이 실제 flags에 반영됐는지 확인합니다.
다음 판단 악화되면 백업한 JVM option 파일과 이전 code release를 복원해 한 번만 재시작합니다. dump·JFR·로그에는 고객 데이터가 있을 수 있어 0600·암호화·보존 기간을 적용하고, 원인은 heap dump 분석·allocation profile·code change로 제거합니다.
배포판과 버전에 따라 옵션·로그 위치가 다를 수 있습니다. 실행 전 서버의 --help와 로컬 매뉴얼을 함께 확인하세요.