티스토리 뷰

Java

쓰레드 덤프(Thread Dump)

siyoon210 2019. 3. 13. 20:48
반응형

참고링크

Orcal 문서 - Using Thread Dumps를 참고하여 번역한 포스팅입니다. 의역 오역이 있을 수 있습니다.

쓰레드 덤프란?

이 챕터는 Oracle JRockit JVM 쓰레드 덤프를 어떻게 얻고 사용하는지에 대해서 설명합니다. 그리고 기본적인 지식으로는 쓰레드와 쓰레드 동기화에 대해서 알고 있어야 합니다.

쓰레드 덤프란 프로세스에 속한 모든 쓰레드들의 상태를 기록(snapshot)한 것입니다. 그리고 각 쓰레드들은 ‘스택 추적(stack trace)’ 형태로 보여집니다. 쓰레드들은 직접 실행시킨 Java 응용 프로그램의 쓰레드와 JVM 내부적으로 존재하는 쓰레드로 나뉘게 됩니다.

쓰레드 덤프는 발생된 문제들을 분석하거나 혹은 응용 프로그램과 JVM의 성능을 최적화 하는데 도움을 주는 정보들을 보여줍니다. 예를 들어, 쓰레드 덤프는 자동적으로 데드락의 발생을 보여줍니다. 데드락(교착 상태)는 응용 프로그램의 일부 혹은 전체를 완전히 중단시킬 수 있습니다. (데드락(교착 상태)란 두 개 이상의 작업이 서로 상대방의 작업이 끝나기 만을 기다리고 있기 때문에 결과적으로 아무것도 완료되지 못하는 상태를 가리킨다. - 위키백과)

쓰레드 덤프 만들기

프로세스로부터 쓰레드 덤프를 만들기 위해서는 아래와 같은 방법중에 한가지 방법을 사용합니다.

  • 프로세스가 구동되고 있는 중에, Ctrl - Break를 누릅니다. (리눅스 상에서 SIGQUIT을 프로세스에 전송하는 방법으로도 가능합니다.)
  • bin\jrcmd.exe <pid> print_threads 커맨드를 입력합니다.

공식 문서에서는 위와 같이 2가지 방법이 나오지만 다른 방법이 더 쓰기 편했기에 같이 소개 하겠습니다.

JDK 1.6이상에서 가능하며, Microsoft Windows에서도 jstack을 이용해 스레드 덤프를 획득할 수 있습니다.

  • 터미널 상에서 jps -v 입력 하면 프로세스의 pid를 알 수 있습니다.
  • 위에서 얻은 pid를 이용 하여 jstack pid 를 입력합니다.

쓰레드 덤프 읽어보기

이 절에서는 처음부터 끝까지 쓰레드 덤프의 예제를 통하여 일반적인 쓰레드 덤프의 내용 설명합니다. 먼저, 예제 쓰레드 덤프가 컴포넌트로 나눠져있다 (목록 20-1, 목록 20-2, 목록 20-3, 목록 20-4, 목록 20-5 참조). 먼저 주 스레드에 대한 정보를 인쇄 한 다음 모든 JVM 내부 스레드와 그 밖의 모든 Java 응용 프로그램 쓰레드 (있는 경우)를 표시합니다. 마지막으로, 락 체인에 대한 정보가 출력된다.

예제 쓰레드 덤프는 신속하게 데드락(교착 상태)가 되는 세 개의 쓰레드를 만드는 프로그램에서 가져온 것입니다. 응용 프로그램 쓰레드 인 Thread-0, Thread-1 및 Thread-2는 Java 코드의 세 가지 다른 클래스에 해당합니다.

쓰레드 덤프의 시작

쓰레드 덤프는 덤프 날짜와 시간, 사용 된 JRockit JVM의 버전 번호로 시작합니다 (목록 20-1 참조).

목록 20-1 쓰레드 덤프의 초기 정보

===== FULL THREAD DUMP ===============
Wed Feb 21 13:46:45 2007
BEA JRockit(R) R27.1.0-109-73164-1.5.0_08-20061129-1428-windows-ia32

주요 응용 프로그램 쓰레드 스택 추적(Stack Trace)

목록 20-2는 주요 응용 프로그램 쓰레드의 스택 추적을 나타냅니다. 제일 먼저 쓰레드 정보 라인이 있으며, 쓰레드 덤프가 발생할 때 락에 대한 정보와 쓰레드의 스택 추적이 이어집니다.

목록 20-2 쓰레드 덤프 내의 주요 쓰레드

"Main Thread" id=1 idx=0x2 tid=48652 prio=5 alive, in native, waiting
-- Waiting for notification on: util/repro/Thread1@0x01226528[fat lock]
at jrockit/vm/Threads.waitForSignal(J)Z(Native Method)
at java/lang/Object.wait(J)V(Native Method)at java/lang/Thread.join(Thread.java:1095)
^-- Lock released while waiting: util/repro/Thread1@0x01226528[fat lock]at java/lang/Thread.join(Thread.java:1148)
at util/repro/DeadLockExample.main(DeadLockExample.java:23)
at jrockit/vm/RNI.c2java(IIII)V(Native Method)
-- end of trace

이름과 기타 식별 정보 이후에, 주요 쓰레드(Main Thread)의 여러 상태 메세지가 출력됩니다. 목록 20-2의 주요 쓰레드는 실행중인 쓰레드 (alive)이며 JVM 내부 코드 또는 사용자 정의 JNI 코드 (in native)를 실행 중이며 현재 객체가 릴리스 될 때까지 대기 중(waiting) 입니다. 쓰레드가 (Object.wait()를 호출하여) 락에 대한 알림을 기다리는 경우 스택 추적 맨 위에 알림 대기 중(Waiting for notification on)으로 표시됩니다.

락 (Locks and Lock Chains)

만약 락(Lock)에 대한 정보를 알고 싶다면 아래 포스팅을 참고하시기 바랍니다.

자바의 락(Locks in Java)


각 쓰레드에 대해서 JRockit JVM은 다음과 같은 정보를 출력합니다.

  • 만약 쓰레드가 락(Lock)을 얻기 위해 시도 할 때 (동기화된 블록에 들어갈려고 할 때), 락이 이미 다른 쓰레드에 의해 잡혀있있다면 이것은 스택 추적의 가장 위에서 “Blocked trying to get lock (락을 얻기 위한 시도 중 막힌)” 상태로 있음을 나타냅니다.
  • 만약 쓰레드가 (Object.wait()를 호출하여) 알림 대기 중(Waiting for notification on)이라면, 이것은 스택 추적의 가장 위에서 “Waiting for notification(알림을 대기중인)” 상태에 있음을 나타냅니다.
  • 만약 쓰레드가 어떠한 락이라도 취한 상태라면, 이것은 스택 추적안에서 나타납니다. 스택 추적 다음 줄에는 함수 호출을 설명하는 락(함수의 쓰레드가 취한)의 목록들입니다. 이런 경우 ^— 기호가 락을 가지고 있음을 나타냅니다. (여기서 ^— 기호는 락이 있는 줄 위에 쓰여진 함수에서 락이 사용된다는 것을 상기시키는 역할을 합니다).

Java에서 객체에 대한 대기(알림)에 의미는 다소 복잡합니다. 먼저, 동기화된 블록에 들어가이 위해서는, 반드시 객체의 락을 취하고 있어야 합니다. 그리고 나서 객체의 wait() 함수를 호출합니다. wait 메소드에서는 쓰레드가 실제로 대기 상태가 되어 알림을 기다리기 전에 락이 해제됩니다. 알림을 받으면 다시 대기 한 다음 락을 다시 가져와 반환합니다. 따라서 쓰레드가 락을 가져 와서 해당 락에 대해 대기 중일 때 (알림), 락이 취해진 된 시점을 설명하는 스택 추적은 “Holding lock(락을 소유하고 있음)”으로 표시되지 않고, “Lock released while waiting(대기 중에 락을 반환함)” 상태로 표시 됩니다.

모든 락들은 Classname@0xLockID[LockType] 형태로 나타납니다. 예를 들면 다음와 같습니다. java/lang/Object@0x105BDCC0[thin lock]

Classname@0xLockID는 락이 속한 객체를 설명합니다. classname은 어떤 클래스의 객체인지 명확하게 나타냅니다. 반면에 LockID는 단일 쓰레드 덤프에서만 유효한 일시적인 ID입니다. 즉, 만약 쓰레드 A가 java/lang/Object@0x105BDCC0 이라는 락을 소유하고 있고, 그리고 쓰레드 B가 java/lang/Object@0x105BDCC0 를 기다리고 있을 때, 이것은 싱글 쓰레드 덤프안에서 같은 락임을 보장합니다.

그러나 이후의 쓰레드 덤프를 수행하는 경우, LockID는 비교할 수 없으며 쓰레드가 동일한 락을 보유하고 있어도 다른 LockID를 가질 수 있으며 반대로 동일한 LockID가 동일한 락을 보유한다는 것을 보장하지는 않습니다.

LockType은 JVM 내부의 락의 유형을 나타냅니다 (fat, thin, recursive, or lazy). 활성 락의 상태(monitors)는 스택 추적에도 나타납니다.

부정확한 락 정보의 표시

컴파일러 최적화로 인해 락 정보가있는 행이 정확하지 않을 수 있습니다.

이것은 아래와 같은 2가지 사실을 말합니다.

  • 같은 함수인 쓰레드에서 락 A를 먼저 취하고 그 다음에 B를 취했다고 하더라도, 출력되는 순서는 특정지어질 수 없습니다.
  • 메소드 foo()가 메소드 bar()를 호출했다고 가정 할 때, bar()가 락 A를 취하고 있을지라도 foo()가 취하고 있는 것으로 출력 될 수 있습니다.

일반적으로 이것은 문제가 되어서는 안됩니다. 락을 출력하는 행의 순서는 절대로 정환한 위치에서 움직여서는 안됩니다. 또한 락을 출력하는 행이 누락되지 않습니다. 쓰레드가 수행한 모든 락이 쓰레드 덤프에 표시된다는 것을 확신 할 수 있습니다.

반응형
댓글