장애 대응 (jstack·jmap·OOM killer·FD)
4. 응용 변형 예제
4.1 덤프 수집 스크립트
장애 때 손으로 치지 않도록 08 레슨 형식으로 묶습니다.
#!/usr/bin/env bash
set -euo pipefail
PID=${1:?JVM PID}; OUT=/var/log/myapp/incident/$(date +%Y%m%d_%H%M%S); mkdir -p "$OUT"
for i in 1 2 3; do jstack "$PID" > "$OUT/td$i.txt"; sleep 5; done
top -H -p "$PID" -b -n 1 | head -n 20 > "$OUT/top-h.txt"
jstat -gcutil "$PID" 1000 5 > "$OUT/gc.txt"
jmap -histo:live "$PID" | head -n 40 > "$OUT/histo.txt"
ls /proc/"$PID"/fd | wc -l > "$OUT/fd.txt"
echo "saved: $OUT"힙 덤프는 앱을 멈추므로 자동에서 빼고 판단 후 수동으로 찍습니다. 이 스크립트를 실행한 뒤 재시작하는 것이 운영 원칙입니다.
4.2 커넥션 풀 고갈
HikariPool-1 - Connection is not available, request timed out 로그가 나올 때입니다.
grep -c 'HikariPool.*getConnection' td1.txt # 커넥션 기다리는 스레드
grep -B2 -A15 'HikariProxyConnection' td1.txt | grep 'at com.shop' | sort | uniq -c | sort -rn | head
ss -tn state established '( dport = :1521 )' | wc -l # 실제 DB 연결 수두 번째 줄이 커넥션을 오래 잡고 있는 앱 코드를 빈도순으로 보여 줍니다. 대개 트랜잭션 안에서 외부 API 를 부르는 코드입니다.
4.3 GC 로그 켜기
재발에 대비해 GC 로그를 항상 남깁니다. JDK 9 이상 통합 로깅 형식입니다.
JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:file=/var/log/myapp/gc.log:time,uptime:filecount=5,filesize=20m"
grep 'Pause Full' /var/log/myapp/gc.log | tail -n 5filecount·filesize 로 스스로 백업(압축·삭제)하므로 06 레슨의 logrotate 대상에서 뺍니다. Full GC 줄의 ms 가 초 단위로 커지면 힙 부족 경고입니다.
4.4 Native Memory 확인
힙은 여유 있는데 RSS 가 계속 늘면 힙 밖 메모리입니다. 스레드 스택, 메타스페이스, 직접 버퍼가 후보입니다.
JAVA_OPTS="$JAVA_OPTS -XX:NativeMemoryTracking=summary" # 시작 시 켜야 함
jcmd 12345 VM.native_memory summary | grep -E 'Thread|Class|Internal|committed'Thread 가 크면 스레드 수가 많은 것이고 -Xss 나 풀 크기를 줄입니다. 켜면 5~10% 오버헤드가 있어 진단 기간에만 씁니다.
4.5 응답 없음인데 CPU 도 낮을 때
CPU 가 낮고 BLOCKED 도 없으면 외부 대기입니다. 스택에서 소켓 읽기를 찾습니다.
grep -B1 -A6 'SocketInputStream.read\|socketRead' td1.txt | grep -E '^"|at com.shop' | head -n 20
ss -tnp | grep 'pid=12345' | awk '{print $5}' | sort | uniq -c | sort -rn | head -n 3같은 원격 주소로 연결이 몰려 있으면 그 외부 서비스가 느린 것입니다. 실무 확장 11 레슨의 타임아웃과 서킷 브레이커가 이 상황의 대책입니다.