📌 Tomcat이 무엇인지, WAS를 진단할 때 어떤 부분들을 확인해야 하는지 등은 강의를 통해 확인할 수 있어요.
이 포스팅에서는 Thread dump에 대해 이야기할게요

1. Thread dump

Thread dump를 통해 Thread가 무슨 일을 하는지 알 수 있어요.

A. Thread 덤프 생성

$ ps -ef | pgrep java
$ jstack [pid] > thread.dump

B. Thread 덤프 내용

# 생성 시간 정보
2021-03-27 23:55:32

# JVM에 대한 정보
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.251-b08 mixed mode):

# DeadLock에 대한 정보
Found one Java-level deadlock:
=============================
"http-nio-8080-exec-6":
  waiting to lock monitor 0x00007faa62973608 (object 0x00000005c0c4b1e8, a java.lang.Object),
  which is held by "http-nio-8080-exec-5"
"http-nio-8080-exec-5":
  waiting to lock monitor 0x00007faa60025508 (object 0x00000005c0c4b1f8, a java.lang.Object),
  which is held by "http-nio-8080-exec-6"

Java stack information for the threads listed above:
===================================================
"http-nio-8080-exec-6":
	at nextstep.subway.line.ui.LineController.findLockLeft(LineController.java:78)
	- waiting to lock <0x00000005c0c4b1e8> (a java.lang.Object)

# Thread stack 정보
"http-nio-8080-exec-6" #47 daemon prio=5 os_prio=31 tid=0x00007faa6132c000 nid=0x7f03 waiting for monitor entry [0x0000700009bce000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at nextstep.subway.line.ui.LineController.findLockLeft(LineController.java:78)
	- waiting to lock <0x00000005c0c4b1e8> (a java.lang.Object)
	- locked <0x00000005c0c4b1f8> (a java.lang.Object)
두 스레드의 교착
두 스레드의 교착exec-5 · BLOCKED · exec-6 · BLOCKED · L1 · …b1e8 · exec-5 · L2 · …b1f8 · exec-6 · 순환 대기 · 실선: 락 → 점유자 · 점선: 스레드 → 기다리는 락. 서로 점유한 락을 기다립니다. 어느 쪽도 자동으로 풀리지 않습니다.exec-5BLOCKEDexec-6BLOCKEDL1 · …b1e8exec-5L2 · …b1f8exec-6순환 대기실선: 락 → 점유자점선: 스레드 → 기다리는 락
두 스레드의 교착exec-5 · BLOCKED · exec-6 · BLOCKED · L1 · …b1e8 · exec-5 · L2 · …b1f8 · exec-6 · 순환 대기 · 실선: 락 → 점유자 · 점선: 스레드 → 기다리는 락. 서로 점유한 락을 기다립니다. 어느 쪽도 자동으로 풀리지 않습니다.exec-5BLOCKEDexec-6BLOCKEDL1 · …b1e8exec-5L2 · …b1f8exec-6순환 대기실선: 락 → 점유자점선: 스레드 → 기다리는 락

서로 점유한 락을 기다립니다. 어느 쪽도 자동으로 풀리지 않습니다.

L1: 0x00000005c0c4b1e8
L2: 0x00000005c0c4b1f8
exec-5/6: http-nio-8080-exec-5/6

단계별 장면

1. exec-5가 L1, exec-6가 L2를 점유합니다.

두 스레드의 교착 · 1exec-5 · L1 점유 · exec-6 · L2 점유 · L1 · …b1e8 · exec-5 · L2 · …b1f8 · exec-6 · 락 요청 · 실선: 락 → 점유자 · 점선: 스레드 → 기다리는 락. exec-6가 L2를 점유합니다.exec-5L1 점유exec-6L2 점유L1 · …b1e8exec-5L2 · …b1f8exec-6락 요청실선: 락 → 점유자점선: 스레드 → 기다리는 락
두 스레드의 교착 · 1exec-5 · L1 점유 · exec-6 · L2 점유 · L1 · …b1e8 · exec-5 · L2 · …b1f8 · exec-6 · 락 요청 · 실선: 락 → 점유자 · 점선: 스레드 → 기다리는 락. exec-6가 L2를 점유합니다.exec-5L1 점유exec-6L2 점유L1 · …b1e8exec-5L2 · …b1f8exec-6락 요청실선: 락 → 점유자점선: 스레드 → 기다리는 락

2. exec-5는 L1을 보유한 채 L2를 기다립니다.

두 스레드의 교착 · 2exec-5 · BLOCKED · exec-6 · L2 점유 · L1 · …b1e8 · exec-5 · L2 · …b1f8 · exec-6 · 락 요청 · 실선: 락 → 점유자 · 점선: 스레드 → 기다리는 락. exec-5는 L1을 보유한 채 L2를 기다립니다.exec-5BLOCKEDexec-6L2 점유L1 · …b1e8exec-5L2 · …b1f8exec-6락 요청실선: 락 → 점유자점선: 스레드 → 기다리는 락
두 스레드의 교착 · 2exec-5 · BLOCKED · exec-6 · L2 점유 · L1 · …b1e8 · exec-5 · L2 · …b1f8 · exec-6 · 락 요청 · 실선: 락 → 점유자 · 점선: 스레드 → 기다리는 락. exec-5는 L1을 보유한 채 L2를 기다립니다.exec-5BLOCKEDexec-6L2 점유L1 · …b1e8exec-5L2 · …b1f8exec-6락 요청실선: 락 → 점유자점선: 스레드 → 기다리는 락

3. exec-6도 L1을 기다리면 순환 대기가 닫힙니다. L1=0x00000005c0c4b1e8 / L2=0x00000005c0c4b1f8. 원문 dump는 그대로 둡니다.

두 스레드의 교착 · 3exec-5 · BLOCKED · exec-6 · BLOCKED · L1 · …b1e8 · exec-5 · L2 · …b1f8 · exec-6 · 순환 대기 · 실선: 락 → 점유자 · 점선: 스레드 → 기다리는 락. 서로 점유한 락을 기다립니다. 어느 쪽도 자동으로 풀리지 않습니다.exec-5BLOCKEDexec-6BLOCKEDL1 · …b1e8exec-5L2 · …b1f8exec-6순환 대기실선: 락 → 점유자점선: 스레드 → 기다리는 락
두 스레드의 교착 · 3exec-5 · BLOCKED · exec-6 · BLOCKED · L1 · …b1e8 · exec-5 · L2 · …b1f8 · exec-6 · 순환 대기 · 실선: 락 → 점유자 · 점선: 스레드 → 기다리는 락. 서로 점유한 락을 기다립니다. 어느 쪽도 자동으로 풀리지 않습니다.exec-5BLOCKEDexec-6BLOCKEDL1 · …b1e8exec-5L2 · …b1f8exec-6순환 대기실선: 락 → 점유자점선: 스레드 → 기다리는 락
원본 thread dump의 소유·대기 관계입니다. 락을 획득한 순서는 최종 관계를 설명하기 위한 연출입니다.
  • Thread 이름, 식별자, 우선순위(prio), Thread가 점유하는 메모리 주소를 의미하는 Thread ID(tid), OS에서 관리하는 Thread ID (nid), Thread 상태 (NEW | RUNNABLE | BLOCKED | WAITING | TIMED_WAITING | TERMINATED) 등의 정보를 확인할 수 있어요.

C. 실습

$ cd docker && docker-compose up -d

a. 테스트

# 상황 1
$ curl localhost:8080/lines

# 상황 2
$ curl localhost:8080/lines/lock-left
$ curl localhost:8080/lines/lock-right

# 상황 3
여러 브라우저 혹은 터미널로 요청해본다.
$ curl localhost:8080/lines/tan
$ curl localhost:8080/lines/tan
...

b. Thread dump 로컬로 download

# 운영서버에서 Bastion으로 파일 옮기기
$ rsync -avzh [원격대상경로] [받을경로]
# 예)
$ rsync -avzh ubuntu@2.2.2.2:/home/ubuntu/log/ ./log/

# Bastion 서버에서 파일 다운로드
$ scp -i [pem키] ubuntu@1.1.1.1:/home/ubuntu/log/thread.dump .

2. Arthas 활용하기

A. Arthas 설치 및 실행

  • VisualVM을 사용해주셔도 좋습니다.

    $ curl -O https://arthas.aliyun.com/arthas-boot.jar
    $ java -jar arthas-boot.jar

B. dashboard

  • 쓰레드 상태, 메모리 그리고 시스템의 전반적인 상황을 확인할 수 있어요.

  • 우선 CPU 사용량, 누적 시간이 많은 쓰레드를 확인해보세요

  • 새로운 쓰레드를 계속 생성할 경우엔 쓰레드 풀을 사용해야 합니다

C. thread

$ thread --all
$ thread -b
  • RUNNABLE이 높을 경우 : 정상적인 상태이나, CPU 사용량이 비정상적으로 높을 경우 확인해봐야 합니다.

  • BLOCKED가 높을 경우 : Lock 상태의 쓰레드이므로 원인을 파악해봅니다.

  • WAITING / TIMED_WAITING : 문제되지 않아요.

D. trace