LabHub
배우기 러닝패스 코스

힙은 남았는데 서비스가 멈췄다 · 스레드 덤프로 멈춘 이유를 찾는다 · 실습

스레드 덤프로 멈춘 이유를 찾는다

LabHub 에서 이어서 보기

목표

"힙은 남았는데 서비스가 멈췄다" 를 직접 만들고 스레드 덤프로 원인을 짚는다 — 잠금을 쥔 스레드, 줄 선 스레드, 교착, 그리고 tryLock 타임아웃으로 하는 복구.

왜 중요한가

프로세스도 포트도 살아 있는데 응답이 없을 때, 힙 그래프는 아무것도 말해 주지 않는다. 스레드 덤프만이 "누가 무엇을 기다리는가" 를 보여 준다. 재시작하면 증거가 사라지므로, 멈춘 순간에 덤프를 찍는 손이 먼저다. 재료 Stuck.java 는 127.0.0.1:8085 에서 처리 스레드 4개(worker-N)로 동작하는 HTTP 서비스다 — /health 는 DB 를 건드리지 않고, / 는 DB 잠금을 잡고 5ms 일하며, /poison 은 잠금을 잡은 채 영원히 잠든다. -Ddb.lock.timeout.ms=<ms> 를 주면 synchronized 대신 ReentrantLock.tryLock 을 써서 그 시간 안에 못 잡으면 503 을 돌려준다.

단계

1. /root/jvm/threads 에 Stuck.java 를 컴파일하고 nohup java -cp /root/jvm/threads Stuck > /root/jvm/threads/server.log 2>&1 & 로 띄운 뒤 pid 를 /root/jvm/threads/server.pid 에 적는다. curl -s http://127.0.0.1:8085/health 가 ok 를 돌려줘야 한다.
2. jcmd -l > /root/jvm/threads/jcmd.txt 로 JVM 목록을 남긴다. Stuck 이 server.pid 의 pid 로 보여야 한다.
3. 정상 상태의 덤프를 jcmd <pid> Thread.print > /root/jvm/threads/dump-idle.txt 로 남긴다. Full thread dump"worker-1" 이 있어야 한다.
4. 사건을 만든다: curl -s -m 20 http://127.0.0.1:8085/poison & 로 잠금을 영원히 쥐게 한 뒤 curl -s -m 20 http://127.0.0.1:8085/ & 를 세 번 더 보낸다(전부 백그라운드). 2초 뒤 jcmd <pid> Thread.print > /root/jvm/threads/dump-stuck.txt. 덤프에 waiting to lock 이 3줄 이상, BLOCKED 이 있어야 하고 /health 도 응답하지 않게 된다.
5. dump-stuck.txt 에서 - locked <주소> (a Stuck$Db) 를 쥔 스레드 이름과 같은 주소를 waiting to lock 하는 스레드 수를 읽어 /root/jvm/threads/diagnosis.txtholder=<스레드이름>blocked=<n> 두 줄로 적는다. 채점기는 살아 있는 프로세스의 덤프와 대조한다.
6. Deadlock.java 를 컴파일해 java -cp /root/jvm/threads Deadlock > /root/jvm/threads/deadlock.log 2>&1 & 로 띄우고(그대로 둔다) 1초 뒤 jcmd <pid> Thread.print > /root/jvm/threads/dump-deadlock.txt. Found 1 deadlock. 이 있어야 한다.
7. 복구: server.pid 의 프로세스를 kill 하고 -Ddb.lock.timeout.ms=500 을 붙여 다시 띄워 server.pid 를 갱신한다. 다시 /poison 을 백그라운드로 보낸 뒤 curl -s -m 5 -o /dev/null -w '%{http_code}' http://127.0.0.1:8085/ 의 결과를 /root/jvm/threads/after.txt 에 저장한다 — 이제 503 이 5초 안에 돌아와야 한다.
8. 서비스가 떠 있는 채로 /root/jvm/threads/dumps/dump-1.txt, dump-2.txt, dump-3.txt 를 2초 간격으로 남긴다. 세 파일의 시각 줄(pid 줄 다음의 둘째 줄)이 서로 달라야 한다.

참고

단계 8개

  1. 서비스를 띄우고 pid 를 남긴다
  2. JVM 목록
  3. 정상 상태의 덤프
  4. 사건을 만들고 덤프를 찍는다
  5. 잠금을 쥔 스레드와 줄 선 스레드
  6. 교착은 JVM 이 이름을 불러 준다
  7. 무한 대기를 503 으로 바꾼다
  8. 덤프는 세 장