태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

[ Enterprise Java는 거대한 동기화 머신이다 - Thread ] Enterprise Java & Oracle 성능 분석의 기초 - Part3

Enterprise Java 2007.08.27 12:59
Enterprise Java는 거대한 동기화 머신이다.

Thread Dump
Java에서 Thread 동기화 문제를 분석하는 가장 기본적인 툴은 Thread Dump를 분석하는 것이다. Java Application의 Thread Dump는 아래 방법으로 얻을 수 있다.
Thread Dump는 기본적으로 현재 사용 중인 Thread의 상태와 Stack Trace를 출력해준다. 더불어 JVM의 종류에 따라 더욱 풍부한 정보를 같이 제공한다.

Simple Application
아래에 Thread 동기화 문제를 일으키는 간단한 두 개의 예제가 있다.

Case1: Synchronized에 의한 동기화
public class dump_test {
static Object lock = new Object();
public static void main(String[] args) {
new Thread2().start();
try { Thread.sleep(10); } catch(Exception ex) {}
new Thread1().start();
new Thread1().start();
new Thread1().start();
}
}

class Thread1 extends Thread {
int idx = 1;
public void run() {
while(true) {
synchronized(dump_test.lock) { // <-- Thread1은 synchronized 블록으로 인해 Thread2의 작업이 끝나기를 기다린다.
System.out.println(idx++ + " loopn");
}
}
}
}


class Thread2 extends Thread {
public void run() {
while(true) {
synchronized(dump_test.lock) { // <-- Thread2는 synchronized 블록을 이용해 긴(Long) 작업을 수행한다.
for(int idx=0; idx
}
}
}
}

Case2: wait/notify에 의한 동기화
public class dump_test2 {
static Object lock = new Object();
public static void main(String[] args) {
new Thread2().start();
try { Thread.sleep(10); } catch(Exception ex) {}
new Thread1().start();
new Thread1().start();
new Thread1().start();
}
}


class Thread1 extends Thread {
int idx = 1;
public void run() {
while(true) {
synchronized(dump_test2.lock) {
System.out.println(idx++ + " loopn");
try { dump_test2.lock.wait(); } catch(Exception ex) {} // <-- wait 메소드를 이용해 notify가 이루어지기를 기다린다.
}
}
}
}


class Thread2 extends Thread {
public void run() {
while(true) {
for(int idx=0; idx<90000000; idx++) {}
synchronized(dump_test2.lock) {
dump_test2.lock.notify(); // <-- notify 메소드를 이용해 WAITING 상태의 Thread를 깨운다.
}
}
}
}

Case1(Synchronized)에서는 Thread1이 BLOCKED 상태에 있게 되며, Case2(Wait/Notify)에서는 Thread1이 WATING 상태에 있게 된다. Java에서 명시적으로 Thread를 동기화시키는 방법은 이 두개의 Case 뿐이다. Thread Pool 동기화에의 의한 Thread 대기, JDBC Connection Pool 동기화에 의한 Thread 대기, EJB Cache/Pool 동기화에 의한 Thread 대기 등 모든 Thread 대기가 이 두 개의 Case로 다 해석 가능하다. 따라서 이 두 Case의 원리만 정확하게 파악하면 어떤 복잡한 Case라도 어렵지 않게 해석이 가능하다.

위의 두 가지 경우가 세 개의 주요 JVM인 Sun HotSpot JVM, IBM JVM, JRockit JVM에서 각각 어떻게 관찰되는지 Thread Dump를 통해 관찰해보자.

Sun HotSpot JVM

Case1: Synchronized에 의한 동기화

아래 Thread Dump는 Sun HotSpot VM에서 Synchronized에 의한 Thread 블로킹이 발생하는 도중의 Thread dump 결과다.
-----------------------------------------------------------------------------------------
Full thread dump Java HotSpot(TM) 64-Bit Server VM (1.5.0_04-b05 mixed mode):

"DestroyJavaVM" prio=1 tid=0x0000000040115580 nid=0x1e18 waiting on condition [0x0000000000000000..0x0000007fbfffd380]

"Thread-3" prio=1 tid=0x0000002afedbd330 nid=0x1e27 waiting for monitor entry [0x00000000410c9000..0x00000000410c9bb0]
at Thread1.run(dump_test.java:22)
- waiting to lock <0x0000002af44195c8> (a java.lang.Object)

"Thread-2" prio=1 tid=0x0000002afeda6900 nid=0x1e26 waiting for monitor entry [0x0000000040fc8000..0x0000000040fc8c30]
at Thread1.run(dump_test.java:22)
- waiting to lock <0x0000002af44195c8> (a java.lang.Object)

"Thread-1" prio=1 tid=0x0000002afeda5fe0 nid=0x1e25 waiting for monitor entry [0x0000000040ec7000..0x0000000040ec7cb0]
at Thread1.run(dump_test.java:22)
- waiting to lock <0x0000002af44195c8> (a java.lang.Object)

"Thread-0" prio=1 tid=0x0000002afeda3520 nid=0x1e24 runnable [0x0000000040dc6000..0x0000000040dc6d30]
at Thread2.run(dump_test.java:38)
- waiting to lock <0x0000002af44195c8> (a java.lang.Object)
...
----------------------------------------------------------------------------------------

Thread-1, Thread-2, Thread-3이 "waiting for monitor entry" 상태, 즉 Monitor에 들어가기 위해 기다리고 있는 상태로 대기 중이다. 즉, "synchronized" 문에 의해 블로킹되어 있음을 의미한다. 이 경우 Thread.getState() 메소드는 BLOCKED 값을 리턴한다. 반면 Thread-0는 현재 "runnable" 상태로 일을 하고 있는 중이다.

또한 Thread-0과 Thread1,2,3 이 동일한 Lock Object(0x0000002af44195c8)에 대해 경합을 하고 있음을 알 수 있다. 즉 동일한 dump_test.lock 오브젝트에 대해 락 경합을 벌이고 있다.


Case2: Wait/Nofity에 의한 동기화

아래 Thread Dump는 Sun HotSpot VM에서 Wait/Notify에 의한 Thread 블로킹이 발생하는 도중의 Thread dump 결과다.
---------------------------------------------------------------------------------------------------

Full thread dump Java HotSpot(TM) 64-Bit Server VM (1.5.0_04-b05 mixed mode):

"DestroyJavaVM" prio=1 tid=0x0000000040115580 nid=0x1c6c waiting on condition [0x0000000000000000..0x0000007fbfffd380]

"Thread-3" prio=1 tid=0x0000002afedb7020 nid=0x1c7b in Object.wait() [0x00000000410c9000..0x00000000410c9db0]
at java.lang.Object.wait(Native Method)
- waiting on <0x0000002af4442a98> (a java.lang.Object)
at java.lang.Object.wait(Object.java:474)
at Thread1.run(dump_test2.java:23)
- locked <0x0000002af4442a98> (a java.lang.Object)

"Thread-2" prio=1 tid=0x0000002afedb5830 nid=0x1c7a in Object.wait() [0x0000000040fc8000..0x0000000040fc8e30]
at java.lang.Object.wait(Native Method)
- waiting on <0x0000002af4442a98> (a java.lang.Object)
at java.lang.Object.wait(Object.java:474)
at Thread1.run(dump_test2.java:23)
- locked <0x0000002af4442a98> (a java.lang.Object)

"Thread-1" prio=1 tid=0x0000002afeda6d10 nid=0x1c79 in Object.wait() [0x0000000040ec7000..0x0000000040ec7eb0]
at java.lang.Object.wait(Native Method)
- waiting on <0x0000002af4442a98> (a java.lang.Object)
at java.lang.Object.wait(Object.java:474)
at Thread1.run(dump_test2.java:23)
- locked <0x0000002af4442a98> (a java.lang.Object)

"Thread-0" prio=1 tid=0x0000002afeda3550 nid=0x1c78 runnable [0x0000000040dc6000..0x0000000040dc6b30]
at Thread2.run(dump_test2.java:36)
...
-----------------------------------------------------------------------------------------

Synchronized에 의한 Thread 블로킹의 사례와 달리, Thread1,2,3 이 "in Object.wait()" 상태에서 대기하고 있다. 즉 BLOCKED 상태가 아닌 WAITING 상태에서 대기하고 있다.
여기서 특별히 주의해야할 것은 Thread1,2,3을 실제로 블로킹하고 있는 Thread가 정확하게 어떤 Thread인지 직관적으로 알 수 없다는 것이다. Thread1,2,3은 비록 대기상태에 있지만, 이는 블로킹에 의한 것이 아니라 단지 Notify가 오기를 기다릴(Wait)뿐이기 때문이다. BLOCKED 상태와 WAITING 상태의 정확한 차이를 이해해야 한다.

(참고) BLOCKED 상태와 WAITING 상태의 정확한 차이를 이해하려면 다음 코드가 의미하는 바를 이해할 필요가 있다.

synchronized(lockObject) {
lockObject.wait();
doSomething();
}

위의 코드가 의미하는 바는 다음과 같다.
  1. lockObject의 Monitor에 우선 들어간다.
  2. lockObject에 대한 점유권을 놓고 Monitor의 Wait Set(대기 리스트)에서 대기한다.
  3. 다른 Thread가 Notify를 해주면 Wait Set에서 나와서 다시 lockObject를 점유한다. 만일 다른 Thread가 이미 lockObject를 점유했다면 다시 Wait Set에서 대기한다.
  4. lockObject를 점유한 채 doSomething()을 수행하고, lockObject의 Monitor에서 빠져나온다.
즉, lockObject.wait() 메소드 호출을 통해 대기하고 있는 상태에서는 이미 lockObject에 대한 점유권을 포기한(Release) 상태이기 때문에 BLOCKED 상태가 아닌 WAITING 상태로 분류된다. 반면 Sycnrhonized 문장에 의해 Monitor에 아직 들어가지도 못한 상태에서는 BLOCKED 상태로 분류된다.

위의 두 Case의 차이를 정확하게 이해하면 Thread Dump를 좀 더 명확하게 해석할 수 있다. 즉, 현재 어떤 종류의 동기화 기법에 의해 Thread 동기화 문제가 발생하고 있는지 파악할 수 있으며, 그에 따른 적절한 해결책을 제시할 수 있다.

------------------------------------------------------------------------------------------------------

다음 글에 계속...

PS)
첨부된 파일은 Thread 동기화 문제를 유발하는 두 개의 Case에 대한 Java Source 파일과 Sun HotSpot JVM, IBM JVM, JRockit JVM에서의 Thread Dump 결과들이다.





신고
tags : ,
Trackback 0 : Comments 3
  1. khk 2011.05.14 02:05 Modify/Delete Reply

    관리자의 승인을 기다리고 있는 댓글입니다

  2. {-String.Split-|- 2011.12.01 16:32 Modify/Delete Reply

    관리자의 승인을 기다리고 있는 댓글입니다

  3. yamanin 2012.02.21 12:28 Modify/Delete Reply

    관리자의 승인을 기다리고 있는 댓글입니다

Write a comment

티스토리 툴바