시냅스

Java 코드와 예제로 보는 Thread Dump 활용 방법 본문

Java, Spring

Java 코드와 예제로 보는 Thread Dump 활용 방법

ted k 2023. 3. 1. 15:56

Thread Dump

thread dump 를 활용하는 상황은 여러가지가 있을 수 있습니다.

JVM이 정상적으로 작동하는데 애플리케이션이 비정상적으로 느리다거나, 

액티브 트랜잭션이 쌓이기만 하는데 메모리나 CPU가 이상현상이 없다거나,

정상 작동하면서 특정 환경에서는 TCP 에러가 발생한다거나 하는 등의 상황이 될 것입니다.

 

이전에 살펴보았던 Heap Dump 에서도 눈으로 확인하는 것이 중요하다는 것을 배웠습니다.

이번에는 Thread Dump 를 통해서 Thread 상태에 대해 확인하는 방법을 배워보겠습니다.

 

https://liltdevs.tistory.com/167

 

JMX 와 VisualVM 그리고 Heap dump 분석하기

JMX jmx는 jdk 1.5 부터 포함된 Java 애플리케이션의 모니터링 관리를 위한 표준 API 이다. 애플리케이션의 상태, 성능 및 리소스 사용 등과 같은 정보를 수집하고 이를 모니터링, 관리 및 제어하는 데

liltdevs.tistory.com

 

 

Thread 의 상태

https://d2.naver.com/helloworld/10963

  • NEW
    • 스레드가 생성되었지만 아직 실행되지 않은 상태
  • RUNNABLE
    • 현재 CPU를 점유하고 작업을 수행 중인 상태. 운영체제의 자원 분배로 인한 WATING 상태가 될 수도 있다.
  • BLOCKED
    • Monitor 를 획득하기 위해 다른 스레드가 락을 해제하기를 기다리는 상태
  • WAITING
    • wait(), join(), park() 메서드 등을 이용해 대기하고 있는 상태
  • TIMED_WAITING
    • WAITING가 다른 점은 시간을 지정해 줄 수 있어 외부 상태 뿐만 아니라 시간에 의해서도 풀릴 수 있음

 

언제 Thread Dump를 해야할까?

  • CPU 사용량이 비정상 적으로 높을 때
    • CPU를 가장 많이 점유하는 스레드가 무엇인지 찾아본다.
  • 수행 성능이 비정상적으로 느릴 때
    • BLOCKED 상태인 스레드 목록을 찾고 BLOCKED 상태인 스레드가 획득하려는 락과 관계된 스레드를 추출한다.

 

Thread Dump 이후 확인할 수 있는 경우들

  • 락을 획득하지 못하는 경우 (Blocked)
    • 한 스레드가 락을 소유하고 있어 다른 스레드가 락을 획득하지 못해 애플리케이션의 전체적인 성능이 느려지는 경우
  • 데드락 상태인 경우
    • 스레드 A가 작업을 계속하려면 스레드 B가 소유한 락을 획득해야 하고, 스레드 B가 작업을 계속하려면 스레드 A가 소유한 락을 획득해야 해서 데드락 상태에 있는 경우 (순환 대기)
  • WAIT 상태에 있는 경우
    • 스레드가 계속 WAIT 상태를 유지하고 있는 경우
  • 스레드 리소스를 정상적으로 정리하지 못하는 경우
    • 메인 스레드가 먼저 끝나고 join 으로 자원 처리 못한 경우
    • 불필요한 스레드가 계속해서 늘어나는 경우
    • 스레드 리소스를 정상적으로 정리 못하고 있는 경우이기 때문에 각 스레드를 정리하는 모습 혹은 스레드가 종료되는 조건을 확인하는 것이 좋다.

 

위의 예시들은 Thread Dump 를 실제로 보면서 확인하는 데에 용이합니다.

아래에서 실제로 확인해보도록 하겠습니다.

 

 

예제

@Service
class SynchService {
	public void test() {
		Thread.currentThread().setName("thread-dump-test"); // thread dump 내에서 구분을 위해 이름 지정
		synchronized (this) {
			try {
				Thread.sleep(100000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

@RestController
class TestController {
	@Autowired SynchService synchService;

	@GetMapping("/")
	public String threadDumpTest(HttpServletResponse response) {
		synchService.test();
		return "test";
	}
}

이번 예제는 액티브 트랜잭션이 쌓이기만 하는 상황으로 가정하겠습니다.

실행 순서는 다음과 같습니다.

  1. HTTP Request를 JMeter 를 사용하여 3번 보냅니다.
  2. 이때 처음으로 들어온 Request(Thread) 는 lock을 획득하여 장시간 sleep 합니다.
  3. 다음으로 들어온 2번 3번 Request(Thread) 는 lock 을 획득하기 위해 대기합니다.

위의 상황을 VisualVM, JMeter 로 실행하여 확인해보겠습니다.

 

 

JMeter 로 3개의 Request를 보낸 결과서입니다.

처리량이 36개 / 1시간 이므로 아주 느리다는 판단을 먼저 할 수 있습니다.

 

 

이후 확인한 VisualVM Monitor 입니다.

아무런 문제가 없어 보입니다.

 

 

스레드를 확인하여 보니 뭔가 이상합니다.

1번 스레드가 장시간 sleep 에 들어가있고, 2번 3번이 monitoring 되고 있지만

장시간 어떤 수행도 하지 않습니다.

이때 Thread Dump 를 실행합니다.

 

 

 

 

 

위와 같은 thread 상태 들을 얻을 수 있습니다.

하나의 Thread 가 sleep 에 들어가있고 2개의 Thread가 Blocked 되어있습니다.

고로 우리는 '하나의 스레드가 sleep 에 들어가 2개가 영향을 받았구나.' 라고 추론해 볼 수 있겠습니다.

 

Comments