Java

[자바 트러블 슈팅] 스레드 진단

hongyb 2025. 5. 26. 14:33

Java 프로세스에서 스레드 단면을 분석하고 문제를 해결하는 방법을 알아보자.

스레드에서 발생할 수 있는 문제

스레드를 처리할 때 발생할 수 있는 문제는 다음과 같다.

 

1. 레이스 컨디션

  • 멀티 스레드 환경에서 ‘공유 데이터’에 아무런 록 처리를 하지 않을 경우 문제 발생
  • 여러 스레드에서 동시에 수정 시 공유 데이터 꼬여서 무한 대기 or 무한 루프

 2. 데드록

  • 두 개 이상의 스레드에서 서로의 록이 풀리기 기다리는 상황
  • 4가지 조건이 필요[비선점, 상호배제, 점유와 대기, 순환 참조]

예시) 스레드 A에서 Data1에 록을 건 상태에서 Data2에 접근하려 한다. 스레드 B에서 Data2에 록을 건 상태에서 Data1에 접근하려 한다.

 3. 스타베이션

  • 스레드가 CPU로부터 일할 기회를 받지 못하는 경우

 4. 라이브 록

  • 응답을 요청한 스레드에서 계속해서 응답을 요청하는 작업이 계속되는 경우
  • 데드록과 달리 CPU를 계속해서 사용한다.

록 경합을 피하는 방법

 1. 코드가 아닌 데이터를 보호

  • 자바에서 메서드를 synchronized로 감싸지 말고 데이터만 감싸라
  • 데이터만 동시성을 막아야지 코드를 막을 필요는 없다.

2. 록 사용에 비싼 계산 하지 마라

  • 공유 데이터를 사용 시 간단한 로직만 구현

3. 록 분리해라

  • 공유 데이터를 하나의 록으로 설정하지 말고 최소한으로 분리해라
  • 배열 전체가 록으로 걸지 말고 배열 요소들이 다른 록을 갖도록 분산해라

4. atomic 작업 사용하라

  • 병렬 프로그래밍에서 제공하는 atomic연산을 사용해라

5. 동기화된 데이터 구조를 사용하라

  • atomic연산을 사용할 수 없으면, 내부적으로 atomic을 사용하는 데이터 구조를 사용해라

6. 읽기-쓰기 록 디자인 패턴을 사용하라

  • 읽기와 쓰기를 위한 사용자를 분리해서 성능 저하 없이 사용 가능하다.

7. 읽기 전용 데이터를 사용하라

  • 록을 걸지 않고 모든 스레드에서 접근할 수 있는 읽기 전용 데이터를 만들어라

8. 지역 변수, 스레드 로컬 저장소를 사용하라

  • 스레드의 로컬에 있는 공유 변수는 다른 것으로 치환 가능하다.
  • ex) 배열에 있는 큰 수를 찾을 때 스레드에서 값 증가할 때마다 큰 수를 따로 저장하면 된다.

스레드 단면 자르기

앞에서 본 스레드의 문제를 찾기 위해 스레드 단면을 분석해야 한다. 시스템에 문제가 발생했을 경우 10~30초 간격으로 최소 열 번 정도 스레드 단면을 생성하자. 다음은 스레드 단면 분석을 해야 하는 경우이다.

  • 시스템 응답 없을 때
  • 시스템의 CPU가 떨어지지 않을 때
  • 애플리케이션 응답 없을 때
  • 시스템이 내 마음대로 작동하지 않을 때

스레드 단면 정보

스레드 단면은 다음 사진과 같다.

 

항목 정보는 다음과 같다.

  1. 스레드 이름
  2. 식별자 : 데몬 스레드일 때만 생성
  3. 스레드 우선순위 : prio 부분에 우선순위를 숫자로 나타낸다. 숫자 높을수록 우선순위 높다.
  4. 스레드 id(tid) : 다른 스레드와 구분되는 스레드 id, 해당 스레드가 점유하는 메모리 주소로 봐도 된다.
  5. 네이티브 스레드 ID(nid) : OS에서 관리하는 스레드의 ID
  6. 스레드 상태
    • NEW : 아직 시작되지 않은 상태
    • RUNNABLE : 수행 중인 상태
    • BLOCKED : 스레드가 잠겨 있어 풀리기 기다리는 상태
    • WAITING : 다른 스레드가 특정 작업 수행하여 깨울 때까지 무한정 기다리는 상태
    • TIMED_WAITING : 다른 스레드가 특정 작업 수행하여 깨울 때까지 지정된 시간만큼 기다리는 상태
    • TERMINATED : 종료
  7. 주소 범위

그 외 at으로 된 부분은 해당 스레드의 스택 정보이다.

스레드 단면 생성 방법

다음 명령어를 입력해 프로세스 id를 구한다

JPS

 

이후 다음과 같이 kill 명령어를 사용하면 된다.

kill -3 [pid]

 

주의할 점은 스레드 단면 생성을 위해 -3 옵션을 써야 한다. -9 옵션을 사용하면 프로세스가 죽는다.

다음과 같은 명령어를 사용해도 된다.

jstack [pid]

 

프로세스의 CPU 사용량을 알기 위해 다음 명령어를 사용한다.

ps -Lf -p [PID]

 

다음처럼 결과가 나온다.

 

항목은 다음과 같다.

  • UID : 사용자 id
  • PID : 프로세스 id
  • PPID : 부모 프로세스 id
  • LWP : 스레드 id
  • C : CPU 사용량
  • NLWP : 해당 프로세스에서 사용하는 스레드 수. NLWP 수
  • STIME : 프로세스 통제하는 터미널

스레드로 문제 해결

스레드로 해결할 수 있는 문제는 다음과 같다.

  • 시스템이 느린 경우
    • 전체 시스템이 항상 느린 경우
    • 시스템 특정 기능이 느린 경우
    • 특정 시간에 전체 애플리케이션이 느린 경우
    • 특정 시간에 특정 애플리케이션이 느린 경우
    • 특정 기능이 점점 느려질 경우
    • 특정 사용자만 느려질 경우
  • 시스템 응답이 없는 경우
    • 모든 애플리케이션이 응답 없는 경우
    • 특정 기능이 응답하지 않는 경우

다음은 스레드 단면으로 해결하기 애매한 경우이다.

  • 예외 계속 발생
    • 모든 사용자가 특정 기능 수행하면 예외 발생
    • 특정 사용자가 특정 기능에서만 예외
    • 특정 시간대에서 전체 애플리케이션 예외
    • 특정 시간대에 특정 애플리케이션에 예외

시스템이 죽는 경우 스레드 단면만으로 해결하지 못한다. 왜냐하면 그 경우 스레드 단면이 생성되지 않기 대문이다. 시스템이 죽는 문제가 발생했을 때 OnError 옵션을 추가해 실마리를 남기자.

-XX:OnError="명령어"
ex) -XX:OnError="kill -e %p"

 

시스템이 느릴 경우 전차

시스템이 느릴 때 다음 순서로 점검하자

  1. CPU, 메모리 리소스 사용량
  2. 외부와 연동하는 리소스 사용량
  3. WAS 메모리 및 스레드 설정 및 사용량
  4. Web 서버 설정 점검
  5. OS 설정 점검
  6. 스레드 상태 점검
  7. 메모리 상태 점검

시스템 응답 없을 때 스레드 단면

스레드 단면은 시스템 응답이 없는 경우에 큰 효과를 보인다. 어떤 스레드에서 응답을 주지 않아 시스템이 멈췄는지 알 수 있다. ex) 스레드 풀이나 DB 커넥션 풀이 꽉 찼을 경우

어떤 순서로 봐야 할지 다음과 같다.

  1. 전체 스레드 개수 확인
  2. 스레드 메모리 사용량 확인
  3. Lock 확인
  4. Runnable 스레드 살펴보기

힙 메모리가 부족해 시스템이 응답하지 않을 수 있다. 이 경우 GC 관련 스레드가 CPU 코어를 하나 이상 100% 점유하고 있다. 이런 상황에 다음 과정을 실행한다.

  1. 스레드 단면 주기적으로 떠놓는다.
  2. ps -Lf -p pid 명령어도 같이 수행한다.
  3. 2번에서 CPU 사용 시간이 지속해서 증가하는 스레드가 있다면 그 스레드 id 확인 후 스레드 단면에서 해당 스레드를 확인한다.
  4. 3번에서 확인한 스레드가 GC관련 스레드라면, jstat 명령어로 메모리 사용량 확인한다. ex) jstat -gcutil 5s
  5. GC 관련 스레드가 아니라면 무한 루프에 빠지지 않았는지 확인한다.

'Java' 카테고리의 다른 글

리눅스 진단 명령어  (0) 2025.06.17
Java 메모리 진단  (0) 2025.06.11