본문 바로가기

Java

자바 성능 튜닝 이야기 책정리

01.디자인 패턴 꼭 써야한다.  [적어도 MVC는 적용하자] 

  • MVC는 Model View Controller로 화면에 모든 로직을 넣어두는 것이  

        아닌 모델 역할,  역할, 컨트롤러 역할을 하는 클래스를 각각 만들어 개발하는 모델이다.  

       (화면)/컨트롤러(뷰와 모델의 전달자)/모델(결과 저장.수정.삭제) 

  • 서블릿 )  

  • J2EE디자인 패턴 )  시스템을 만들기 위해 전체 중  

     일부 의미 있는 클래스들을 묶은 각각의 집합을 디자인 패턴이라 한다. 

     반복되는 의미 있는 집합을 정의하고 이름을 지정해서 누가 이야기 하더라도 

     동일한 의미의 패턴이 되도록 만들어 놓은 것이다. 

  • 성능과 관련된 패턴? ) 

-TransObject Pattern : 객체에 여러 개 값을 전달하는 일을 수행. 

       getter와 setter의 사용으로 정보의 은닉. 

       일일이 null체크 할 필요 없음 

       하나의 객체에 결과를 담을 수 있어 두,세번씩 요청하는 일이 방지 

-Service Locator Pattern : static활용하여 WAS구동 시  

   DataSource같은 context 는 구동 시 한번만 생성됨. 

 

  

02.내가 만든 프로그램의 속도를 알고 싶다. [프로파일링 툴 소개 및 사용법] 

  • 프로파일링(profiling) 툴 )  

소스 레벨의 분석을 위한 툴 

애플리케이션의 세부 응답시간까지 분석가능 

메모리 사용량을 객체나 클래스, 소스의 라인 단위까지 분석 

  • APM(application performance monitoring) 툴) 

애플리케이션의 장애 상황에 대한 모니터링 및 문제점 진단이 주 목적 

서버의 사용자 수나 리소스에 대한 모니터링 

실시간 모니터링 

  • 유용한 System클래스 

System클래스는 생성자가 없으며, 클래스와 변수 모두 static 

  • 배열복사 : System.arraycopy(srcArr,str_idx,dstArr,end_idx) 

  • jvm property 속성 : System.getProperties() 

  • 서버 env 속성 : System.getenv() 

  •  System.currentTimeMills ->ms 1/1000초 

  • System.nanoTime -> ns 1/1000,000,000 

  • 유용한 성능 측정 라이브러리들 

JUnit 

JMH 

  

  • 절대 사용하지 말아야 할 메서드 

static void gc(); 

static void exit(); 

static void Finalization(); 

  

  

  

03.왜 짜꾸 String을 쓰지 말라는 거야. [String vs StringBuffer vs StringBuilder 성능 비교] 

 String – 선언수정시 마다 객체를 생성하므로 짮은 문장을 정의할 때 사용하는것이 좋음 

     1.5이상부터 "+"로 문자열 연결할 경우 자동으로 StringBuilder로 동작함. 

 StringBuffer – ThreadSafe여러개 스레드에서 하나의 StringBuffer객체를 처리해도 문제없음 

      Sb.apend("ABCD"+"EFG"); -> 전혀 효과없음 

StringBuilfer - 단일 스레드에서만 안전성 보장 가능. 

  

04.어디에 담아야 하는지.. [Collection, Map..어디에 담아야 좋을까? Collection,Map 특성] 

배열을 제외하면 리스트형 데이터를 담기 가장 좋은 객체는 Collection와 Map인터페이스를 상속한 객체이다. 

  • Collection : 가장 상위 인터페이스. 

  • Set : 중복을 허용하지 않는 집합을 처리하기 위한 인터페이스. 

  • SortedSet : 오름차순을 갖는 Set 인터페이스 

  • List : 순서가 있는 집합을 처리하기 위한 인터페이스 

         인덱스가 있어 위치 지정 가능.  

        중복 허용 

        가장 많이 사용하는 것. 

  • Queue: 여러 개의 객체를 처리하기 전에 담아서 처리하기 위한 인터페이스. 

                      기본적으로 FIFO를 따름. 

  • Map : 키와 쌍으로 구성됨. 

                   중복 키 허용하지 않음 

  • SortedMap : 오름차순이 가능한 Map 인터페이스 

 

핵심 샘플 정리하면 좋을듯. 

자바 책 보고 예제 찾아보면 좋을듯. 

 

<Set 인터페이스로 구현한 클래스> 

  • 중복 없는 집합 객체를 만들 때 유용함 

HashSet : 데이터를 해쉬 테이블에 담는 클래스로 순서 없이 저장된다. 

LinkedHashSet : 해쉬 테이블에 데이터를 담는데,  

   저장된 순서에 따라서 순서가 결정된다. 

TreeSet : red-black이라는 트리에 데이터를 담는다. 

       값에 따라 순서가 정해진다. 

       데이터를 담으면서 동시에 정렬하므로 HashSet보다 성능상 느림. 

<List 인터페이스로 구현한 클래스> 

  • 배열의 확장판으로 크기가 자동으로 증가함. 

Vector : 객체 생성시에 크기를 지정할 필요가 없는 배열 클래스 

ArrayList : Vector와 비슷하지만 동기화 처리가 되어 있지 않음. 

LinkedList :ArrayList와 동일하지만, Queue 인터페이스를 구현했으므로 FIFO 큐 작업을 수행함. 

<Map 인터페이스로 구현한 클래스> 

  • key와 value쌍으로 저장되는 구조체. 고유한 값과 그값을 설명하는 데이터를 보관할  유용함. 

HashMap : 데이터를 해쉬 테이블에 담는 클래스이다. 

  Hashtable클래스와 다른 점은 null값 허용과  

  동기화되어있지 않다는것이다. 

TreeMap : red-black 트리에 데이터를 담는다. 

 TreeSet와 다른 점을 키에 의해 순서가 정해진다. 

LinkedHashMap : HashMap과 거의 동일하며 이중 연결리스트 라는  

    방식을 사용하여 데이터를 담는다는 점만 다르다. 

Hashtable : 데이터를 해쉬 테이블에 담는 클래스.  

   내부에서 관리하는 해쉬 테이블 객체가 동기화되어 있으므로, 

   동기화가 필요한 부분에서는 이 클래스를 사용. 

<Queue 인터페이스로 구현한 클래스> 

  • 데이터를 담아 두었다가 먼저 들어온 데이터부터 처리하기 위해 사용함. 

PriorityQueue :  

LinkedBlockingQueue :  

ArrayBlockingQueue :  

PriorityBlockingQueue :  

DelayQueue :  

SyncronizedQueue :  

BlockingQueue란 크기가 지정되어있는 큐에 더이상 공간이 없을때 공간이 생길 때 까지 대기하도록 만들어진 큐를 의미한다. 

 

Jpa 

Ibernate 

Rx.java?? 

------------>6.17이후.. 

 

06.static  제대로 한번 써보자. 

  • Static 의 특징 

* static 키워드는 클래스 차원의 변수와 메소드 만들 때 사용한다. 

정적(static)이란 클래스가 로딩될 때 결정된 메모리 주소가 변하지 않음을 의미한다. 

* heap이나 perm영역에 메모리가 할당되지 않으므로, GC의 대상이 아님 

static 변수와 메소드는 객체를 생성하지 않고도 아래와 같이 사용할 수 있다. 

  • 클래스명.static변수 

  • 클래스명.static메소드() 

하나의 JVM이나 WAS에서 여러개 스레드가 같은 주소에 존재하는 값을 참조함. 

  그러므로 static을 잘 사용하면 성능을 뛰어나게 향상시킬 수 있지만, 잘못 사용하면 예기치 못한  

  결과를 초래함.(특히 웹환경은 여러 개 스레드에서 하나의 변수에 접근 할 수 있기 때문에..) 

샘플 소스 참고. 

 

  • Static 잘 활용하기(->자주 사용하고 절대 변하지 않는 변수는 final static으로 선언하자.) 

* JNDI명이나 간단한 코드성 데이터들은 final static으로 선언하는 것이 좋음. 

뿐만아니라, 템플릿 성격의 객체를 static으로 선언하는 것도 성능 향상에 많은 도움이 된다. 

  (ex. Velocity사용 시 활용-static을 사용하지 않는 경우 템플릿 호출 시 마다 파싱을 수행하지만  

    static을 활용하는 경우 템플릿을 한번 읽고 활용.. 

   //생략 

   static Template templat; 

   static { 

try{ 

Template = velocity.getTemplate("TemplateFileName"); 

} 

    } 

* 설정 파일 정보도 static으로 관리하자. 

코드성 데이터는 DB에서 한 번만 읽자. 

jvm메모리 상의 static변수 위치 -> JVM>RunTime Data Area> Method Area > class variable 

     

 

Method Area는 타입 정보를 가지고 있는 영역 

 

상세 참고 URL : http://itpangpang.xyz/71 

 * CodeManager클래스는 코드 정보를 미리 담아 놓는 클래스임. 

 

   아래와 같은 로직으로 로딩되서 호출 된다고 하면... 

   1.static 블록 초기화, 객체 초기화. 

   2.getCodes()수행 

   3.getCodeValue()수행 

   4.(변경 시)updateCodes() 

 

 

   문제는..  

      여러 개의 인스턴스로 구성된 서버의 경우 updateCodes()시 시점 차이로 인하여 

      인스턴스마다 다른 코드값을 가질 수 있음. 

   해결책으로는..  

      코드 변경 시 무조건  WAS재기동하면 이상없음, 

      메모리 캐시를 많이 사용함. 

 

  • Static 잘못쓰면 이렇게 된다. 

여러개의 JVM으로 구동하거나 WAS구성의 시스템에서  

   값이 변할 수 있는 변수에 static을 선언하면  

   변수에 접근이 쉬워지고.. 값이 변경될 경우 변경으로 인해 발생하는 문제점을 확인하기가 어려움 

  Ex) private static boolean successFlag; 

  • Static과 메모리릭 

* static은 GC대상이 아니다.  

   그런데 만약 컬렉션 객체를 static으로 선언하면..?  

   ->객체에 데이터가 쌓이면  GC도 안되고.. 시스템은 OOM을 발생 시킨다. 

      (시스템 재시작 하는수밖에..) 

         . 더 이상 사용 가능한 메모리가 없어지는 현상을 Memory leak이라 함. 

            ->Memory가 급격하게 줄어들었다면.. HeapDump로 분석한다. 

  ->이클립스 MAT Tool.. 

 

07.클래스 정보, 어떻게 알아낼 수 있나? 

    * JVM에 로딩된 클래스와 메서드의 정보를 확인 할 수 있는 API-Class, Method 클래스가 있음.  

     Class 

Method 

ava.lang.Object 

java.lang.Class<T> 

 

java.lang.Object 

java.lang.reflect.AccessibleObject 

Java.lang.reflect.Method 

*reflection으로 메서드 invoke시  매개변수의 확장변환은 되나 

축소변환은 되지않음.(->IlligalArgumentsExceotion..발생) 

    

  • Reflection 관련 클래스들 

Class, Method.. 

  • Reflection 관련 클래스를 사용한 예 

샘플 소스 참고 

  • Reflection 클래스를 잘못 사용한 사례 

일반적으로 로그를 프린트 할 때 Class 클래스를 많이 사용함. 

  this.getClass().getName();  

(->getClass() 호출 시 객체 생성 후 객체의 이름을 가져오는 메서드를 수행하는 시간과 메모리를 사용함.) 

 

잘못사용한 예)  

public String checkClass(Object src){ 

If(src.getClass().getName().equals("java.math.BigDecimal"){…} 

} 

(->이렇게 사용할 경우 응답속도에 그리 많은 영향을 주지는 않으나, 많이 사용하면 필요 없는 시간을 낭비하게 된다.) 

바르게 사용한 예) 

public String checkClass(Object src){ 

If(src instanceof java.math.BigDeciaml){…} 

} 

 

 

08.synchronized는 제대로 알고 써야 한다. 

    여러개의 스레드가 동작하는 시스템에서 syncronized할 경우 성능에 영향을 미칠 수 있다. 

   * syncronized 

     스레드를 동기화 하기 위해 자바에서 제공함. 

     자바에서 동기화란 여러 개의 스레드가 한 개의 자원을 사용하고자 할 때 해당 스레드만 제외하고  

     나머지는 접근을 못하도록 막는 것. 
 

  1. synchronized foo(){ 
        //메쏘드 내용. 
     

  2. foo(){ 
        synchronized(this){ 
            //내용 
        } 
    } 

->1과 2는 같은 얘기.. 

 

참고 : http://egloos.zum.com/iilii/v/4071694 

 

   * 스레드/프로세스 

     . 클래스를 수행시키거나 WAS를 기동하면 프로세스가 하나 생성된다. 

     하나의 프로세스에는 여러개의 스레드가 생성되는데,  

     단일 스레드가 생성되어 종료될 수 도 있고 여러개의 스레드가 생성되어 수행 될 수 도있다. 

       따라서, 프로세스와 스레드의 관계는 1대 다 관계. 

     스레드는 Lightweight Process(LWP)라고도 한다. 

     프로세스에서 만들어 사용하고 있는 메모리를 공유 하므로 프로세스가 하나씩 뜨는 것보다는  

     성능이나 자원사용에 있어서 많은 도움이 된다. 

 

     . 자바 프로세스(=스레드) 생명 주기 

 

 

  • 자바에서 스레드는 어떻게 사용하나? 

 

자바 스레드의 생성자 

- Thread() : Thread를 상속받은 고유의 클래스 생성 필요. 클래스 내부에 b()같은 함수 정의 

- Thread(Runnable target) : Runnable이 들어간 경우는 인터페이스 구현 

- Thread(Runnable target, String name) : Runnable이 들어간 경우는 인터페이스 구현 

- Thread(String name) 

- Thread(ThreadGroup group, Runnable target) 

- Thread(ThreadGroup group, Runnable target, String name) 

- Thread(ThreadGroup group, Runnabel target, String name, long stackSize) 

- Thread(ThreadGroup group, String name) 

 

.Thread 클래스 상속과 Runnable인터페이스 구현 

 Thread는 Runnable 인터페이스를 구현한 것이므로 어느 것을 사용해도 크게 차이 없음. 

스레드 생성과정 

  

 

스레드 생성(2가지 방법) 

1) White box (Thread 상속 이용방법) 

스레드를 상속받는 클래스를 작성합니다. 

- run() 메소드를 오바라이딩하여 내용부를 구현합니다. 

- main() 메소드 내부에서 스레드를 상속받은 클래스의 객체를 생성합니다. 

- 해당 객체의 start() 메소드를 호출합니다. 

 

-Thread 클래스의 대표 메소드 

메소드 

설명 

void start() 

쓰레드 생성하고 run() 메소드를 실행시키는 메소드 

void run() 

쓰레드가 할 일을 정의한 메소드 

String getName() 

쓰레드의 이름을 반환 

void setName() 

쓰레드의 이름을 설정 

void sleep(long millis) 

1000분의 millis 초 만큼 쓰레드를 중지시킨 후, 재실행 

boolean isAlive() 

쓰레드가 살아있는지 확인 

 
 

2) Black box (Runnable 구현 작성방법) 

- Runnable을 구현하는 클래스를 작성합니다. 

- run() 메소드를 오바라이딩하여 내용부를 구현합니다. 

- main() 메소드에서 Runnable을 구현한 클래스의 객체를 생성합니다. 

- Thread 객체를 생성하여 매개변수로 대입합니다. 

- Thread 객체의 start() 메소드를 호출합니다. 

 

 

 

  • Interrupt()메서드를 절대적인 것이 아니다. 

.  스레드의 상태가 block된상태에서만 스레드 중단이 가능하다. 

  • synchronized를 이해하자. 

. 사용 예) 

public syncronized void sampleMethod(){  //메서드 동기화 

…  

} 

 

private Object = new Object(); 

public void sampleBlock(){ 

Synchronized(obj){  //특정부분만 동기화함 

…  

} 

} 

 

  • 동기화는 이렇게 사용한다 - 동일 객체 접근 시 

  • 동기화는 이렇게 사용한다 - static 사용 시 

  • 동기화를 위해서 자바에서 제공하는 것들 

  •  JVM내에서 synchronized 은 어떻게 동작할까? 

 

09.IO에서 발생하는 병목현상 

  • 기본적인 IO는 이렇게 처리한다. 

  • IO에서 병목이 발생한 사례 

  • 그럼 NIO의 원리는 어떻게 되는 거지? 

  • DirectByBuffer를 잘못 사용하여 문제가 발생한 사례 

  • lastModified() 메서드의 성능 저하 

 

10.로그는 반드시 필요한 내용만 찍자. 

  • System.out.println()의 문제점 

  • System.out.format() 메서드 

  • 로그를 더 간결하게 처리하는 방법 

  • 로거 사용 시의 문제점 

  • 로그를 깔끔하게 처리하게 도와주는 slf4j와 LockBack 

  • 예외 처리도 이렇게? 

  •  

11.jsp와 서블릿, Spring에서 발생 할 수 있는 여러 문제점 

  • JSP와 서블릿의 기본 동작원리는 꼭 알아야 한다. 

  • 적절한 include사용하기 

  • 자바 빈즈, 잘 쓰면 약 못 쓰면 독 

  • 태그 라이브러리도 잘 써야 한다. 

  • 스프링 프레임워크를 사용하면서 발생할 수 있는 문제점들 

  •  

12.DB를 사용하면서 발생 가능한 문제점들 

  • DB Connection과 Connection Pool, DataSource 

  • DB를 사용할 때 닫아야 하는 것 들 

  • JDK7에서 등장한 AutoClosable 인터페이스 

  • ResultSet.last()메서드 

  • JDBC를 사용하면서 유의해야할 만한 몇가지 팁 

 

 

*)HTTP통신 에러 상태 코드 

  • 200번대 : 정상적인 경우  

  • 300번대 : 리다이렉션이 필요한 경우의 리턴코드 

           (대부분 302나 304코드다. 만약 브라우저의 캐시를 사용하여  

    이미지나 css, js파일 등을 다시 서버에 요청하지 않는 경우에 이 코드가 리턴된다.) 

  • 400번대 : 클라이언트 오류가 있을 경우(대표적인 404. 서버에 존재하지 않는 주소 요청 시) 

  • 500번대 : 서버에 오류가 있을 경우(서버에서 예외가 발생 했을 경우 페이지 미처리 시)








>>


Java와 스레드 

웹서버에서는 수많은 요청을 처리하기 위해 수백개의 스레드가 동시에 수행이 된다. 

빠른 처리를 위해 동시에 처리되지만 한정된 자원을 동시에 사용하는 스레드의 경우 스레드간에 경합이  

발생한다. 여기서 경합이란 하나의 스레드A가 자원C를 사용하는데 다른 스레드에 의해서 자원C가 손상되지 않도록 lock을 거는것을 의미한다.  스레드A가 자원C의 사용을 끝내면 lock을 해제하게 되고, 스레드B가 자원C를 사용할 수 있다. ( 대표적으로 로그를 기록하는 것) 

 

스레드 동기화 

여러 스레드가 공유 자원을 사용할 때 자원이 정합성을 보장하기 위해서는 스레드 동기화로  

한번에 하나의 스레드만 공유 자원에 접근할 수 있도록 해야한다. 

Java에서는 Monitor를 이용해 스레드를 동기화하는데, 

Java객체는 하나의 Monnitor를 가지고 있다.  

다른 스레드가 자원사용을 위해 Monitor를 획득하려면 Monitor를 획득하고 있는 스레드가 Monitor를 해제할 때까지 Wait queue에서 대기해야한다. 

 

스레드 상태 

스레드 덤프를 분석하려면 스레드의 상태를 알아야 한다. 스레드의 상태는 java.lang.Thread 클래스 내부에 State라는 이름을 가진 Enumerated Types(열거형)으로 선언되어 있다. 

그림 1 스레드 상태 

  • NEW: 스레드가 생성되었지만 아직 실행되지 않은 상태 

  • RUNNABLE: 현재 CPU를 점유하고 작업을 수행 중인 상태. 운영체제의 자원 분배로 인해 WAITING 상태가 될 수도 있다. 

  • BLOCKED: Monitor를 획득하기 위해 다른 스레드가 락을 해제하기를 기다리는 상태 

  • WAITING: wait() 메서드, join() 메서드, park() 메서드 등를 이용해 대기하고 있는 상태 

  • TIMED_WAITING: sleep() 메서드, wait() 메서드, join() 메서드, park() 메서드 등을 이용해 대기하고 있는 상태. WAITING 상태와의 차이점은 메서드의 인수로 최대 대기 시간을 명시할 수 있어 외부적인 변화뿐만 아니라 시간에 의해서도 WAITING 상태가 해제될 수 있다는 것이다. 

  • TERMINATED : 쓰레드는 sleep , wait , join 또는 park 메소드  사용하여 대기 중입니다. ( 대기와 의 차이 는 최대 대기 시간이 method 매개 변수에 의해 지정되고 대기 는 외부 변경뿐만 아니라 시간에 의해 완화 될 수 있다는 것입니다.)  

 

*덤프에는 종료된 어플리케이션은 당연히 보이지 않음. 

 Stack에 쌓인 스레드들만 보여주는것임. 

 

 

스레드의 종류 

-데몬 스레드(Daemon Thread) 

  다른 비데몬 스레드가 없다면 동작을 중지한다.  

  사용자가 직접 스레드를 생성하지 않더라도 Java 애플리케이션이 기본적으로 여러개의 스레드를 생성한다. 

  대부분이 데몬 스레드인데가비지 컬렉션이나 JMX등의 작업을 처리하기 위한 것이다. 

-비데몬 스레드(Non-daemon Thread) 

  Static void main(String[] args) < - 이 형식으로 실행되는 스레드가 비데몬 스레드이다. 

  이 스레드가 동작을 중지하면  다른 데몬 스레드도 같이 중지하게 되는 것이다. 

 

스레드 덤프 획득 

문제가 되는 스레드를 파악하기 위해서는 5-10초간격으로 덤프를 발생시키는 것이 좋음. 

(문제가 되니 stack에 계속 머물러 있기 때문...) 

 

jstack을 이용하는 방법 

1.프로세스 id확인 

  Jps –v 

2.jstack 

  Jstack pid >> test.txt 

*리눅스에서는 Kill –3 pid로 덤프를 획득할 수 있음. 

  

 

스레드 덤프의 정보 

"pool-1-thread-13" prio=6 tid=0x000000000729a000 nid=0x2fb4 runnable [0x0000000007f0f000] 

java.lang.Thread.State: RUNNABLE 

at java.net.SocketInputStream.socketRead0(Native Method) 

at java.net.SocketInputStream.read(SocketInputStream.java:129) 

at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:264) 

 

  • 스레드 이름스레드의 고유 이름. java.lang.Thread 클래스를 이용해 스레드를 생성하면 Thread-(Number) 형식으로 스레드 이름이 생성된다. java.util.concurrent.ThreadFactory 클래스를 이용했으면 pool-(number)-thread-(number) 형식으로 스레드 이름이 생성된다. 

  • 우선순위스레드의 우선순위 

  • 스레드 ID스레드의 ID. 해당 정보를 이용해 스레드의 CPU 사용, 메모리 사용 등 유용한 정보를 얻을 수 있다. 

  • 스레드 상태스레드의 상태. 

  • 스레드 콜스택스레드의 콜스택(Call Stack) 정보. 

 

 

 

스레드 덤프 유형별 패턴 

락을 획득하지 못하는 경우(BLOCKED) 

한 스레드가 락을 소유하고 있어 다른 스레드가 락을 획득하지 못해 애플리케이션의 전체적인 성능이 느려지는 경우이다. 

 

 

 
다음 스레드 덤프 예에서는 BLOCKED_TEST pool-1-thread-1 스레드가 <0x0000000780a000b0> 락을 소유한 상태에서 동작하고 있어, BLOCKED_TEST pool-1-thread-2 스레드와 BLOCKED_TEST pool-1-thread-3 스레드는 <0x0000000780a000b0> 락을 획득하기 위해 대기하고 있는 상태이다. 

"BLOCKED_TEST pool-1-thread-1" prio=6 tid=0x0000000006904800 nid=0x28f4 runnable [0x000000000785f000] 

java.lang.Thread.State: RUNNABLE 

at java.io.FileOutputStream.writeBytes(Native Method) 

at java.io.FileOutputStream.write(FileOutputStream.java:282) 

at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:65) 

at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:123) 

- locked <0x0000000780a31778> (a java.io.BufferedOutputStream) 

at java.io.PrintStream.write(PrintStream.java:432) 

- locked <0x0000000780a04118> (a java.io.PrintStream) 

at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:202) 

at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:272) 

at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:85) 

- locked <0x0000000780a040c0> (a java.io.OutputStreamWriter) 

at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:168) 

at java.io.PrintStream.newLine(PrintStream.java:496) 

- locked <0x0000000780a04118> (a java.io.PrintStream) 

at java.io.PrintStream.println(PrintStream.java:687) 

- locked <0x0000000780a04118> (a java.io.PrintStream) 

at com.nbp.theplatform.threaddump.ThreadBlockedState.monitorLock(ThreadBlockedState.java:44) 

- locked <0x0000000780a000b0> (a com.nbp.theplatform.threaddump.ThreadBlockedState) 

at com.nbp.theplatform.threaddump.ThreadBlockedState$1.run(ThreadBlockedState.java:17) 

at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) 

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) 

at java.lang.Thread.run(Thread.java:662) 

  

Locked ownable synchronizers: 

- <0x0000000780a31758> (a java.util.concurrent.locks.ReentrantLock$NonfairSync) 

  

"BLOCKED_TEST pool-1-thread-2" prio=6 tid=0x0000000007673800 nid=0x260c waiting for monitor entry [0x0000000008abf000] 

java.lang.Thread.State: BLOCKED (on object monitor) 

at com.nbp.theplatform.threaddump.ThreadBlockedState.monitorLock(ThreadBlockedState.java:43) 

- waiting to lock <0x0000000780a000b0> (a com.nbp.theplatform.threaddump.ThreadBlockedState) 

at com.nbp.theplatform.threaddump.ThreadBlockedState$2.run(ThreadBlockedState.java:26) 

at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) 

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) 

at java.lang.Thread.run(Thread.java:662) 

  

Locked ownable synchronizers: 

- <0x0000000780b0c6a0> (a java.util.concurrent.locks.ReentrantLock$NonfairSync) 

  

"BLOCKED_TEST pool-1-thread-3" prio=6 tid=0x00000000074f5800 nid=0x1994 waiting for monitor entry [0x0000000008bbf000] 

java.lang.Thread.State: BLOCKED (on object monitor) 

at com.nbp.theplatform.threaddump.ThreadBlockedState.monitorLock(ThreadBlockedState.java:42) 

- waiting to lock <0x0000000780a000b0> (a com.nbp.theplatform.threaddump.ThreadBlockedState) 

at com.nbp.theplatform.threaddump.ThreadBlockedState$3.run(ThreadBlockedState.java:34) 

at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) 

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) 

at java.lang.Thread.run(Thread.java:662) 

  

Locked ownable synchronizers: 

- <0x0000000780b0e1b8> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)  

 

 

데드락 상태인 경우 

스레드 A가 작업을 계속하려면 스레드 B가 소유한 락을 획득해야 하고,  

스레드 B가 작업을 계속하려면 스레드 A가 소유한 락을 획득해야 해서 데드락 상태에 있는 경우이다. 

 

다음 스레드 덤프 예에서 DEADLOCK_TEST-1 스레드는 <0x00000007d58f5e48> 락을 소유하고 있으며,  

DEADLOCK_TEST-2 스레드가 소유한 <0x00000007d58f5e60> 락을 획득하려 한다.  

한편 DEADLOCK_TEST-2 스레드는 <0x00000007d58f5e60> 락을 소유하고 있으며,  

DEADLOCK_TEST-3 스레드가 소유한 <0x00000007d58f5e78> 락을 획득하려 한다.  

그리고 DEADLOCK_TEST-3 스레드는 <0x00000007d58f5e78> 락을 수유하고 있으며,  

DEADLOCK_TEST-1 스레드가 소유한 <0x00000007d58f5e48> 락을 획득하려 한다. 

 

이렇게 서로 상대가 소유하고 있는 락을 획득하기 위해 대기하기 때문에 한 스레드가 자신의 락을 해제하기 전까지는  

데드락 상태가 해제되지 않게 된다. 

"DEADLOCK_TEST-1" daemon prio=6 tid=0x000000000690f800 nid=0x1820 waiting for monitor entry [0x000000000805f000] 

java.lang.Thread.State: BLOCKED (on object monitor) 

at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.goMonitorDeadlock(ThreadDeadLockState.java:197) 

- waiting to lock <0x00000007d58f5e60> (a com.nbp.theplatform.threaddump.ThreadDeadLockState$Monitor) 

at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.monitorOurLock(ThreadDeadLockState.java:182) 

- locked <0x00000007d58f5e48> (a com.nbp.theplatform.threaddump.ThreadDeadLockState$Monitor) 

at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.run(ThreadDeadLockState.java:135) 

  

Locked ownable synchronizers: 

- None 

  

"DEADLOCK_TEST-2" daemon prio=6 tid=0x0000000006858800 nid=0x17b8 waiting for monitor entry [0x000000000815f000] 

java.lang.Thread.State: BLOCKED (on object monitor) 

at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.goMonitorDeadlock(ThreadDeadLockState.java:197) 

- waiting to lock <0x00000007d58f5e78> (a com.nbp.theplatform.threaddump.ThreadDeadLockState$Monitor) 

at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.monitorOurLock(ThreadDeadLockState.java:182) 

- locked <0x00000007d58f5e60> (a com.nbp.theplatform.threaddump.ThreadDeadLockState$Monitor) 

at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.run(ThreadDeadLockState.java:135) 

  

Locked ownable synchronizers: 

- None 

  

"DEADLOCK_TEST-3" daemon prio=6 tid=0x0000000006859000 nid=0x25dc waiting for monitor entry [0x000000000825f000] 

java.lang.Thread.State: BLOCKED (on object monitor) 

at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.goMonitorDeadlock(ThreadDeadLockState.java:197) 

- waiting to lock <0x00000007d58f5e48> (a com.nbp.theplatform.threaddump.ThreadDeadLockState$Monitor) 

at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.monitorOurLock(ThreadDeadLockState.java:182) 

- locked <0x00000007d58f5e78> (a com.nbp.theplatform.threaddump.ThreadDeadLockState$Monitor) 

at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.run(ThreadDeadLockState.java:135) 

  

Locked ownable synchronizers: 

- None  

 

 

원격 서버로부터 메시지 수신을 받기 위해 계속 대기하는 경우 

다음 스레드 덤프 예에서는 스레드가 계속 RUNNABLE 상태에 있어 문제가 될 만한 부분이 없는 것처럼 보인다.  

하지만 스레드 덤프를 시간순으로 나열하면, socketReadThread 스레드가 계속 소켓을 읽으려 무한정으로 대기하고 있는 상태이다. 

 

"socketReadThread" prio=6 tid=0x0000000006a0d800 nid=0x1b40 runnable [0x00000000089ef000] 

java.lang.Thread.State: RUNNABLE 

at java.net.SocketInputStream.socketRead0(Native Method) 

at java.net.SocketInputStream.read(SocketInputStream.java:129) 

at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:264) 

at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:306) 

at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:158) 

- locked <0x00000007d78a2230> (a java.io.InputStreamReader) 

at sun.nio.cs.StreamDecoder.read0(StreamDecoder.java:107) 

- locked <0x00000007d78a2230> (a java.io.InputStreamReader) 

at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:93) 

at java.io.InputStreamReader.read(InputStreamReader.java:151) 

at com.nbp.theplatform.threaddump.ThreadSocketReadState$1.run(ThreadSocketReadState.java:27) 

at java.lang.Thread.run(Thread.java:662)  

 

 

WAIT 상태에 있는 경우 

스레드가 계속 WAIT 상태를 유지하고 있는 경우이다. 

다음 스레드 덤프 예에서 IoWaitThread 스레드는 LinkedBlockingQueue 객체에서 메시지를 획득하기 위해 계속 대기하고 있다.  

만약 계속 LinkedBlockingQueue에 메시지가 들어오지 않는 다면 해당 스레드의 상태가 바뀌지 않게 된다. 

"IoWaitThread" prio=6 tid=0x0000000007334800 nid=0x2b3c waiting on condition [0x000000000893f000] 

java.lang.Thread.State: WAITING (parking) 

at sun.misc.Unsafe.park(Native Method) 

- parking to wait for <0x00000007d5c45850> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) 

at java.util.concurrent.locks.LockSupport.park(LockSupport.java:156) 

at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1987) 

at java.util.concurrent.LinkedBlockingDeque.takeFirst(LinkedBlockingDeque.java:440) 

at java.util.concurrent.LinkedBlockingDeque.take(LinkedBlockingDeque.java:629) 

at com.nbp.theplatform.threaddump.ThreadIoWaitState$IoWaitHandler2.run(ThreadIoWaitState.java:89) 

at java.lang.Thread.run(Thread.java:662)  

 

스레드 리소스를 정상적으로 정리하지 못하는 경우 

불필요한 스레드가 계속해서 늘어나는 경우이다. 스레드 리소스를 정상적으로 정리 못하고 있는 경우이기 각 스레드를 정리하는 모습 혹은 스레드가 종료되는 조건을 확인하는 것이 좋다. 

스레드 덤프를 이용한 문제 해결 예제 

상황1: CPU 사용률이 비정상적으로 높을 때 

애플리케이션을 수행할 때 CPU 사용률이 비정상적으로 높다면 스레드 덤프를 이용하여 문제를 파악할 수 있다. 

먼저 다음과 같이 CPU를 가장 많이 점유하는 스레드가 무엇인지 추출한다. 

추출한 결과에서 CPU를 가장 많이 사용하는 LWP(light weight process)와 고유 번호를 확인한다. 고유 번호를 16진수로 변환하면 NID를 얻을 수 있다. 다음 예에서 CPU를 가장 많이 사용하는 LWP의 고유 번호는 10039이고, 이 번호를 16진수 변환한 숫자는 0x2737이다. 

[user@linux ~]$ ps -mo pid,lwp,stime,time,cpu -C java 

  

PID LWP STIME TIME %CPU 

10029 - Dec07 00:02:02 99.5 

10039 Dec07 00:00:00 0.1 

- 10040 Dec07 00:00:00 95.5 

...  

 

그 다음으로 스레드 덤프를 얻어 스레드의 동작을 확인한다. 다음은 PID가 10029인 애플리케이션의 스레드 덤프이다 .이 스레드 덤프에서 NID가 0x2737인 스레드를 찾아 스레드의 동작을 확인한다. 

"NioProcessor-2" prio=10 tid=0x0a8d2800 nid=0x2737 runnable [0x49aa5000] 

java.lang.Thread.State: RUNNABLE 

    at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method) 

at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:210) 

at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:65) 

at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:69) 

- locked <0x74c52678> (a sun.nio.ch.Util$1) 

- locked <0x74c52668> (a java.util.Collections$UnmodifiableSet) 

- locked <0x74c501b0> (a sun.nio.ch.EPollSelectorImpl) 

at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:80) 

at external.org.apache.mina.transport.socket.nio.NioProcessor.select(NioProcessor.java:65) 

at external.org.apache.mina.common.AbstractPollingIoProcessor$Worker.run(AbstractPollingIoProcessor.java:708) 

at external.org.apache.mina.util.NamePreservingRunnable.run(NamePreservingRunnable.java:51) 

at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) 

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) 

at java.lang.Thread.run(Thread.java:662)  

 

스레드 덤프를 시간 별로 여러 번 획득해서 스레드의 동작 상태 변화를 확인해 문제 상황을 추론해야 한다. 

상황2: 수행 성능이 비정상적으로 느릴 때 

애플리케이션의 수행 성능이 비정상적으로 느릴 때에는 BLOCKED 상태인 스레드가 원인인 경우가 많다. 이 때에는 스레드 덤프를 여러 번 얻은 다음 BLOCKED 상태인 스레드 목록을 찾고 BLOCKED 상태인 스레드가 획득하려는 락과 관계된 스레드를 추출해 본다. 

아래 스레드 덤프 예에서는 <0xe0375410> 락을 획득하지 못해 계속 BLCOKED 상태에 있다. 해당 락을 획득하고 있는 스레드의 스택 트레이스(Stack Trace) 분석하면 문제를 해결할 수 있다. 

" DB-Processor-13" daemon prio=5 tid=0x003edf98 nid=0xca waiting for monitor entry [0x000000000825f000] 

java.lang.Thread.State: BLOCKED (on object monitor) 

at beans.ConnectionPool.getConnection(ConnectionPool.java:102) 

- waiting to lock <0xe0375410> (a beans.ConnectionPool) 

at beans.cus.ServiceCnt.getTodayCount(ServiceCnt.java:111) 

at beans.cus.ServiceCnt.insertCount(ServiceCnt.java:43) 

  

"DB-Processor-14" daemon prio=5 tid=0x003edf98 nid=0xca waiting for monitor entry [0x000000000825f020] 

java.lang.Thread.State: BLOCKED (on object monitor) 

at beans.ConnectionPool.getConnection(ConnectionPool.java:102) 

- waiting to lock <0xe0375410> (a beans.ConnectionPool) 

at beans.cus.ServiceCnt.getTodayCount(ServiceCnt.java:111) 

at beans.cus.ServiceCnt.insertCount(ServiceCnt.java:43) 

  

" DB-Processor-3" daemon prio=5 tid=0x00928248 nid=0x8b waiting for monitor entry [0x000000000825d080] 

java.lang.Thread.State: RUNNABLE 

at oracle.jdbc.driver.OracleConnection.isClosed(OracleConnection.java:570) 

- waiting to lock <0xe03ba2e0> (a oracle.jdbc.driver.OracleConnection) 

at beans.ConnectionPool.getConnection(ConnectionPool.java:112) 

- locked <0xe0386580> (a java.util.Vector) 

- locked <0xe0375410> (a beans.ConnectionPool) 

at beans.cus.Cue_1700c.GetNationList(Cue_1700c.java:66) 

at org.apache.jsp.cue_1700c_jsp._jspService(cue_1700c_jsp.java:120)  

이런 패턴은 DBMS를 다루는 애플리케이션에서 자주 나타나는 데, 다음은 가장 자주 나타나는 두가지 경우이다. 

첫째, 스레드가 계속 동작하고 있지만 DBCP 등의 설정이 적절하지 못해 충분한 성능을 내지 못하는 경우이다. 이 경우에는 스레드 덤프를 여러 번 얻어서 비교하면 BLOCKED 상태에 있던 스레드 중 몇 개는 다른 상태인 경우가 많을 것이다. 

둘째, DBMS와 연결이 비정상적인 상태라 계속 타임아웃(timeout) 시간까지 대기하는 경우이다. 이 경우에는 스레드 덤프를 여러 번 추출해 비교해도 DBMS와 관련된 스레드는 계속해서 BLOCKED 상태에 있는 것을 확인할 수 있다. 타임아웃 값 등을 적절하게 변경해서 문제 발생 시간을 줄일 수 있다. 

스레드 덤프 분석을 쉽게 만드는 코딩 

스레드에 이름 부여하기 

java.lang.Thread 클래스를 이용해 스레드 객체를 생성하면 Thread-(Number) 라는 형식으로 스레드 이름이 생성된다. 그리고 java.util.concurrent.DefaultThreadFactory 클래스를 이용해 스레드 객체를 생성하면 pool-(Number)-thread-(Number)라는 형식으로 스레드 이름이 생성된다. 

애플리케이션당 적으면 수십 개, 많으면 수천 개가 되는 스레드를 분석할 때, 스레드 이름이 모두 이렇게 기본값으로 되어 있다면 분석 대상이 될 스레드를 구별하기 어렵게 된다. 그렇기 때문에 스레드 생성 시에는 반드시 스레드에 이름을 부여하는 습관을 가지는 것이 좋다. 

java.lang.Thread 클래스를 이용해 스레드를 생성할 때에는 생성자의 인수를 이용해 스레드에 이름을 부여할 수 있다. 

public Thread(Runnable target, String name); public Thread(ThreadGroup group, String name); public Thread(ThreadGroup group, Runnable target, String name); public Thread(ThreadGroup group, Runnable target, String name, long stackSize);  

java.util.concurrent.ThreadFactory 클래스를 이용해 스레드 객체를 생성할 때에는 자신만의 ThreadFactory 클래스를 생성해 이름을 부여하면 된다. 크게 특별한 기능이 필요하지 않다면 다음과 같은 MyThreadFactory를 사용할 수 있다. 

import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; public class MyThreadFactory implements ThreadFactory { private static final ConcurrentHashMap POOL_NUMBER = new ConcurrentHashMap(); private final ThreadGroup group; private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; public MyThreadFactory(String threadPoolName) { if (threadPoolName == null) { throw new NullPointerException("threadPoolName"); } POOL_NUMBER.putIfAbsent(threadPoolName, new AtomicInteger()); SecurityManager securityManager = System.getSecurityManager(); group = (securityManager != null) - securityManager.getThreadGroup() : Thread.currentThread().getThreadGroup(); AtomicInteger poolCount = POOL_NUMBER.get(threadPoolName); if (poolCount == null) { namePrefix = threadPoolName + " pool-00-thread-"; } else { namePrefix = threadPoolName + " pool-" + poolCount.getAndIncrement() + "-thread-"; } } public Thread newThread(Runnable runnable) { Thread thread = new Thread(group, runnable, namePrefix + threadNumber.getAndIncrement(), 0); if (thread.isDaemon()) { thread.setDaemon(false); } if (thread.getPriority() != Thread.NORM_PRIORITY) { thread.setPriority(Thread.NORM_PRIORITY); } return thread; } }  

MBean을 이용한 더 자세한 정보 획득하기 

MBean을 이용하면 ThreadInfo 객체를 획득할 수 있으며, ThreadInfo 객체를 이용하면 스레드 덤프에서 얻기 힘든 정보들을 추가로 얻을 수 있다. 예를 들어, ThreadInfo 객체의 메서드를 이용해 스레드가 BLOCKED 상태나 WAIT 상태로 된 시간의 정보를 얻을 수 있고, 이를 이용해 비정상적으로 긴 시간 동안 동작하고 있지 않은 스레드 목록을 얻을 수도 있다. 

ThreadMXBean mxBean = ManagementFactory.getThreadMXBean(); long[] threadIds = mxBean.getAllThreadIds(); ThreadInfo[] threadInfos = mxBean.getThreadInfo(threadIds); for (ThreadInfo threadInfo : threadInfos) { System.out.println( threadInfo.getThreadName()); System.out.println( threadInfo.getBlockedCount()); System.out.println( threadInfo.getBlockedTime()); System.out.println( threadInfo.getWaitedCount()); System.out.println( threadInfo.getWaitedTime()); }  

 
 

 

 

 

 

Case Sample Study – thread dump띄우고 분석해보기 

  1. 다량의 DB를 read하고있는 상태 

  2. 객체간의 레퍼런싱이 급증가하는 상태 

  3. 같은 테이블의 데이터를 여러개의 스레드를 띄워 IUD를 발생시키는 상태 

  4. 3상태에서 Select를 하는 상태 


'Java' 카테고리의 다른 글

java8  (0) 2018.07.08