장애가 발생했을 때나 기대보다 웹 애플리케이션이 느리게 동작할 때, 우리는 스레드 덤프를 분석해 봐야 한다.
웹서버에서 많은 수의 동시 사용자를 처리하기 위해서 수십 ~ 수백개 정도의 쓰레드를 사용함.
두 개 이상의 쓰레드가 같은 자원을 이용할 때에는 필욘적으로 쓰레드 간의 Contection(경합)이 발생하고 데드락이 발생할 수 도 있다.
*경합 : 한 쓰레드가 다른 쓰레드를 획득하고 있는 락이 해제되기를 기다리는 상태
웹앱에서 여러 쓰레드가 공유자원에 접근하는 일은 매우 빈번한다. 대표적으로 로그를 기록하는는 쓰레드가 락을 획득하고 공유 자원에 접근하는 것이다.
데드락은 쓰레드 경합의 특별한 케이스이다. 두 개 이상의 쓰레드에서 작업을 완료하기 위해 상대의 작업이 끝나야 하는 상황.
쓰레드 경합 때문에 다양한 문제가 발생할 수 있어서 이런 문제를 분석하기 위해서 쓰레드 덤프를 이용함.
배경 지식
동기화 : 여러 쓰레드가 공유 자원을 사용할 때 정합성을 보장하려면 쓰레드 동기화로 한번에 하나의 쓰레드만 공유
자바에서는 Monitor를 통해서 동기화한다. 모든 자바 객체는 하나의 모니터를 가지고 있다. 그리고 모니터는 하나의 쓰레드만 소유할 수 있다.
어떠한 쓰레드가 소유한 모니터를 다른 쓰레드가 획득 하려면 해당 모니터를 소유하고 있는 쓰레드가 모니터를 해제할 때 까지 Wait Queue에서 대기하고 있다.
* 모니터 : 하나의 객체 마다 하나의 모니터를 결합할 수 있고, 모니터는 그것이 결합된 객체가 동시에 두 개 이상의 스레드에 접근할 수 없도록 잠금 기능을 제공.
객체에 모니터를 결합하면 하나의 스레드가 그 데이터를 사용하는 동안에 다른 스레드들이 그 객체를 사용할 수 없게 된다.
자바에서는 synchroinzed 메소드가 선언된 객체와 블럭에 의해 동기화 되는 모든 객체에 고유한 모니터가 결합되어 동기화 작업을 수행한다.
모니터의 구성
스레드 단위로 모니터락을 획득 하거나 반환 한다.
동기화 코드를 수행할 때에는 동기화 대상 인스턴스와 결합된 모니터락을 획득한 후에 진입이 가능하며, 코드를 벗어날 때에는 모니터 락을 반환하게 된다.
동기화 대상 인스턴스 별로 이와 관련된 모니터락이 존재하며 해당 모니터는 현재 락을 획득한 스레드와 락 카운트 정보를 관리한다.
모니터가 락 카운트 정보를 유지한다는 것은 동일 스레드가 중복해서 락을 걸 수 있다는 의미이다.
쓰레드 상태
- NEW: 스레드가 생성되었지만 아직 실행되지 않은 상태
- RUNNABLE: 현재 CPU를 점유하고 작업을 수행 중인 상태. 운영체제의 자원 분배로 인해 WAITING 상태가 될 수도 있다.
- BLOCKED: Monitor를 획득하기 위해 다른 스레드가 락을 해제하기를 기다리는 상태
- WAITING: wait() 메서드, join() 메서드, park() 메서드 등를 이용해 대기하고 있는 상태
- TIMED_WAITING: sleep() 메서드, wait() 메서드, join() 메서드, park() 메서드 등을 이용해 대기하고 있는 상태. WAITING 상태와의 차이점은 메서드의 인수로 최대 대기 시간을 명시할 수 있어 외부적인 변화뿐만 아니라 시간에 의해서도 WAITING 상태가 해제될 수 있다는 것이다.
스레드의 종류
스레드는 데몬 스레드와 비데몬 스레드로 나눌 수 있다
데몬 스레드는 다른 비데몬 스레드가 없다면 동작을 중지한다. 사용자가 직접 스레드를 생성하지 않더라도, Java 애플리케이션이 기본적으로 여러개의 스레드를 생성한다. 대부분이 데몬 스레드 인데 가비지 컬렉션이나, JMX등의 작업을 처리하기 위한 것이다.
static void main 메서드가 실행되는 스레드는 비데몬 스레드로 생성되고, 이 스레드가 동작을 중히자면 다른 데몬 스레드도 동작을 중지하게 되는 것이다.
스레드 덤프 획득
스레드 덤프는 획득할 당시의 스레드 상태만 알 수 있기 때문에 스레드 상태 변화를 확인하려면 5초 간격으로 5~10회 정도 획득하는게 좋다
jstack을 이용하는 방법
- 쓰레드 덤프의 정보
- 쓰레드 이름
- 우선순위
- 쓰레드 ID
- 쓰레드 상태
- 쓰레드 콜스택
쓰레드 덤프를 이용한 문제 해결 예제
- 상황 1: CPU 사용률이 비정상적으로 높을 때
- 앱 수행할 때 CPU 사용률이 비정상적으로 높다면 스레드 덤프를 이용하여 문제를 파악할 수 있다.
- 먼저 다음과 같이 CPU를 가장 많이 점유하는 스레드가 무엇인지 추출한다.
- 추출한 결과에서 CPU를 많이 사용하는 LWP와 고유번호를 확인한다.
- 고유번호를 16진수로 변환하면 NID를 얻을 수 있다.
- 상황 2 : 수행 성능이 비정상적으로 느릴 때
- 애플리케이션 성능이 비정상적으로 느릴 때 BLOCKED 상태인 쓰레드가 원인인 경우가 많다. 이때에는 쓰레드 덤프를 여러번 얻은 다음 BLOCKED 상태인 쓰레드 목록을 찾고 BLOCKED 상태인 쓰레드가 획득하려는 락과 관련된 쓰레드를 추출해본다.
- 이 패턴은 DBMS를 다루는 애플리케이션에서 자주 나온다.
- 첫째 : 쓰레드가 계속 동작하고 있지만 DBCP 등의 설정이 적절하지 못해 충분한 성능을 내지 못하는 경우이다. 이 경우에는 스레드 덤프를 여러번 얻어서 비교하면 BLOCED 상태에 있던 스래드 중 몇 개는 다른 상태인 경우가 많을 것이다.
- 둘째 : DBMS와 연결이 비정상적인 상태라 계속 타임아웃 시간까지 대기하는 경우이다. 이 경우에는 쓰레드 덤프를 여러번 추출해 비교해도 DBMS와 관련된 쓰레드는 계속해서 BLOCKED 상태에 있는 것을 확인할 수 있다. 타임아웃 값 등을 적절하게 변경해서 문제 발생 시간을 줄일 수 있다.
스레드 덤프 유형별 패턴
- 락을 획득하지 못하는 경우 (BLOCKED)
- 한 스레드가 락을 소유하고 있어 다른 스레드가 락을 획득하지 못해 애플리케이션의 전체적인 성능이 느려지는 경우
- 데드락 상태인 경우
- 스레드 A가 작업을 계속하려면 스레드 B가 소유한 락을 획득해야 하고, B가 작업을 계속하려면 스레드 A가 소유한 락을 획득해야 데드락 상태에 있는 경우이다.
- 원격서버로부터 메세지를 수신을 받기 위해 계속 대기하는 경우
- WAIT 상태에 있는 경우
- 스레드가 계속 WAIT 상태를 유지하고 있는 경우
- 스레드가 리소스를 정상적으로 정리하지 못하는 경우
- 불필요한 스레드가 계속해서 늘어나는 경우. 각 스레드를 정리하는 모습 혹은 스레드가 종료되는 조건을 확인하는 것이 좋다.
'개발' 카테고리의 다른 글
TCP, UDP (0) | 2021.04.12 |
---|---|
컨텍스트 스위칭이란 (0) | 2021.04.12 |
CPU (0) | 2021.04.10 |
URL (0) | 2021.04.07 |
지금한강은 개인정보처리방침 (1) | 2021.03.24 |