[Java] 데드락(DeadLock) 트러블 슈팅 해보기

아두치·2023년 9월 6일
0

HTTP 기반 백엔드 시스템에서 데드락 현상이 발생하는 경우는 드물지만, "자바 트러블 슈팅 이야기" 와 "자바 최적화" 책을 읽으면서 데드락 상태에서 원인을 진단하고 해결하는 방법에 대해서 공부한 겸 정리해보고자 한다.

사실 그동안 특정 API 호출했을 때 응답이 없거나 시스템 자체가 멈춰버렸을 때, 그리고 로그 파일에 단서가 보이지 않을때 만병통치약 처럼 보이는 "재시작" 을 자주 사용했었고 재시작 이후 증상이 없어진 것을 확인하면 그냥 뭔가 꼬여버렸겠거니 하면서 넘긴적이 많았다. (물론 그래도 증상이 계속 되면 어떻게든 찾으려 노력은 했지만..)

이번 글의 내용은 이러한 상황에서 "재시작" 이라는 "요행" 이 아니라 "원인" 을 진단해서 "해결" 하는 과정을 담았다.

우선 내용을 전개하기 전에 몇가지 전제해야하는 상황이 있다.

  1. 특정 API 호출 시 응답이 없음
  2. 해당 API 는 외부 시스템과 연계하는 코드가 없음
  3. 로그 파일엔 마땅한 단서가 남아있지 않음
  4. 애플리케이션이 실행중인 서버는 Ubuntu OS이며 scouter 가 설치되어 있음

여기서 4번은 크게 중요하지 않다. 어떤 OS 여도 상관은 없고, scouter가 설치되어 있지 않더라도 기타 호스트의 리소스를 모니터링 할 수 있는 도구가 있다면 그걸 사용해도 된다.

가장 먼저 해야할 일은 뭘까?

🚀 scouter 지표 확인

데드락이 원인이란걸 모르는 상황이기 때문에 우선 scouter 를 통해 리소스 사용량을 먼저 확인해보자.

지표를 보니 CPU, Memory 모두 응답을 늦출만한 수치는 아니다.
실습을 단순화 하기 위해 네트워크 관련 지표는 따져보지 않을 예정이다.

🚀 스레드 덤프 생성

보통 메모리나 CPU 에 특이점이 없을 경우 스레드의 문제일 가능성이 높다.
스레드 덤프 생성 비용은 힙 덤프 생성 비용에 비해 공짜라고 볼 수 있기 때문에 우선 스레드 덤프를 생성해보자.

jstack 11952 > /jstack/thread_dump_20230906

위 명령어를 터미널에 입력하면 /jstack 디렉터리에 덤프 파일이 생성된다.
11952 는 PID 를 입력하면 되고 덤프 파일 경로나 이름은 마음대로 바꿔도 된다.

🚀 스레드 덤프 분석

이제 위에서 생성한 스레드 덤프를 분석할 차례이다.
나는 가난한 개발자이기 때문에 무료 분석 툴을 사용할건데, 그 중에서 ThreadLogic 이라는 툴을 사용할 것이다.
자바로 만들어져있어서 ThreadLogic 깃헙에서 jar 파일만 다운로드 받아서 실행하면 된다. 아주 간단하다..

https://github.com/sparameswaran/threadlogic/releases

링크로 들어가서 jar파일을 다운로드 받고 실행해보자.

그럼 이런 화면이 뜨는데 당황하지 말고 좌측 상단의 CPU 칩 모양 버튼 (Open Logfile)을 클릭해서 위에서 생성한 덤프 파일을 열자. (혹은 File->Open)

덤프 파일을 열면 해당 덤프 파일을 분석해서 리포팅을 해주는데, 왼쪽 상단을 보면 스레드 덤프 파일 생성 당시의 모든 스레드에 대한 정보를 확인할 수 있는 메뉴들이 제공된다.

하나하나 눌러보면 대충 감이 올건데 여기서 나는 맨 아레이 있는 Thread Groups Summary -> Non-WLS Thread Groups 를 선택했다. (위에 있는 Threads 메뉴를 클릭해도 된다.)
해당 메뉴를 클릭하면 사진에서 오른쪽 화면과 같이 현재 주의가 필요하거나 문제가 있는 스레드들의 목록을 보여준다.
(만약 모든 스레드를 보고 싶다면 그 위에 있는 Minimum Health Level 을 IGNORE로 변경하면 된다. 이 설정은 전역 설정이다.)

이 스레드들 중에서 Scouter 관련 스레드를 제외한 Thread-7,8 이 FATAL 상태인것을 확인할 수 있다.
해당 두 스레드를 드래그하면 아래에 스레드 상태에 대한 설명과 스택 트레이스 정보를 보여주는데 이것만 봐도 사실 어느 코드에서 BLOCK 이 발생했는지 알 수 있다.

나의 경우 DemoApplication.java 파일의 30번째 줄과 47번째 줄에서 문제가 있다고 나와 있다.

그렇다. 나는 이 실습을 위해 Rest API 환경을 셋팅하기가 귀찮아서 애플리케이션을 실행하면 무조건 데드락이 걸리게끔 main 메소드에 구현해 놓았다 ㅜㅜ

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    final static String R1 = "Hello Welcome to Scaler!";
    final static String R2 = "Visit Scaler!";

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);

        // creating thread T1
        Thread T1 = new Thread(){
            // implementing run method
            public void run(){

                // thread T1 locking the R1 resource
                synchronized (R1){
                    System.out.println("Thread T1 locked ->   Resource R1");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

                    // thread T1 locking the R2 resource
                    synchronized (R2){
                        System.out.println("Thread T1 locked -> Resource R2");
                    }
                }
            }
        };

        // creating thread T2
        Thread T2 = new Thread(){
            // implementing run method
            public void run(){

                // thread T2 locking the R2 resource
                synchronized (R2){
                    System.out.println("Thread T2 locked -> Resource R2");

                    // thread T2 locking the R1 resource
                    synchronized (R1){
                        System.out.println("Thread T1 locked -> Resource R1");
                    }
                }
            }
        };

        // starting both the threads
        T1.start();
        T2.start();
    }

}

사실 이 예제도 GeeksForGeeks 의 데드락 강좌에서 가져온 것이다.
흐흐.. 나는야 게으름뱅이

🚀 글 마무리

물론 실무에서는 더 복잡한 환경과 각종 변수가 도사리고 있으니 이렇게 순탄하게 진단되진 않을 것 같지만.. 제자리에서 표적을 맞추는 사격 연습을 해놔야 전쟁통에 뭐라도 하지 않을까 싶다.
참고로 ThreadLogic 으로 진단할 수 있는 스레드 관련 문제는 데드락 말고도 매우 많으니 이것저것 눌러보고 실습해보자.

(다음 글은.. 메모리 릭 진단하는 과정을 쓸까 생각중이다.)

profile
HAVE YOU TRIED IT?

0개의 댓글