'Technical Note/JAVA'에 해당되는 글 17건

Technical Note/JAVA

Q : 특정 쿼리가 PreparedStatement로 실행하면 너무 느린데 같은 쿼리를 Statement 로 실행시는 1초도 안걸립니다. 이유는?



A1 : 첫번째 생각할 것은 최적화기의 문제입니다. 오라클의 경우엔 규칙기반 최적화와, 비용기반 최적화기가 있는데 각각을 rule-based optimizer와 cost-based optimzer가 있습니다. 그리고 이런 최적화기를 선택하는 기준을 설정하기 위한 파라미터로 optimizer_goal 인가하는 파라미터가 있는데 이 값이 all_rows인 경우 쿼리를 olap성으로 파악해서 throughput을 최대화하고, first_rows인 경우에는 oltp성으로 파악해서 response time을 최소화 합니다. 둘다 maximize되는 모드는 없습니다. 예를 드는게, 테이블을 읽을 때 어떻게 읽을까 하는 전략인데 all_rows의 경우에는 full table scan을 지향하고, first_rows에서는 index scan을 사용합니다. 룰 기반 최적화기는 이와 달리, 각종 스캔방식에 점수를 미리 매겨놓고 이 점수대로 최적화하는 것입니다. 가령, 어떤 행을 인덱스로 읽는게 10점이면 풀 테이블 스캔은 5점 이런식입니다. 따라서 인덱스가 있다면 인덱스를 무조건 씁니다. (점수가 높으니까요.) 하지만 인덱스라는게 반환하는 행의 개수가 전체 10~15%일때 인덱스 사용의 효과가 나타나는게 일반적인데, 이 것까지는 룰 기반 최적화에서는 고려하지 않습니다. 다시 말해 옛날 방식이란 거죠. 별다른 설정을 하지 않았다면 아마도 최적화 모드가 all_rows일 텐데 반응시간을 높이고 싶다면 이것을 first_rows로 바꿔주어야합니다. 바꾸는 방법은 oracle 8i 이하에서는 init{SID}.ora (여기서 {SID}자리에는 실제 SID를 적어주면 됩니다. 예를들어 ORCL이라면 initORCL.ora가 됩니다)를 열어서 수정하고 데이터베이스를 재시작하면 됩니다. 오라클 9이상부터는 이렇게만 하면 안되고 방식이 조금 바뀌었는데, 자세한건 저도 기억이 안나네요.. 메뉴얼이나 otn에 물어보세요. (otn.oracle.co.kr 가셔서 좌측에 포럼 클릭하시면 됩니다.)
두번째 생각할 것은, prepared statement는 실행계획을 한번 만들고 계속 재사용한다는 것입니다. 따라서 값의 분포등이 바뀌었어도 이런걸 고려 안해주고 예전에 만든 실행계획을 계속 사용합니다. 따라서 새로이 인덱스를 만들었거나 해도 무조건 예전의 실행계획만 쓰게 되죠. 그래서 이걸 제대로 반영하려면 매번 실행 계획을 작성하는 statement를 쓰시거나 아니면 shared pool이라는 오라클내 공유메모리를 비워주면 되는데 alter system flush shared_pool; 이란 명령을 씁니다. ('_'를 빼야하는건지도 모르겠네요. 잘 기억이 안나서.. 죄송.) 

세번째 생각할 내용은, 실행 계획이 매번 잘 바뀌도록 비용을 제대로 산정해주고 있는가의 문제인데, 비용 산정을 위해서는 값의 분포(히스토그램)나 전체 행의 수같은 정보가 필요합니다. 이것을 분석하는 명령은 자체 제공하는 dbms_stats 패키지가 있고, analyze명령이 있고, monitoring 옵션이 있습니다. 제대로 해주려면 이런 비용 측정 정책도 잘 정립해야됩니다. 자세한 내용은 굉장히 많으니 서적이나 asktom.oracle.com에서 검색을 통해서 참고하시기 바랍니다. 마지막으로 말씀드리고 싶은건, 자주 실행하는 명령도 아니고 그러면 그 문장 하나 Statement로 한다고 해서 큰일날 일도 없단 것입니다. 몽땅 다 Statement로 코딩했다면 모르되, 한두개 쿼리가 Statement라고 심각한 성능저하가 생기고 하지는 않습니다. 

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

A2 : BIND 를 쓸 경우 실행계획이 틀어져서 느려지는 그런 경우를 unsafe literal 이라고 합니다. 예를 들어...

select * from emp where empno = :v1

위의 SQL에서 empno 는 emp의 PK입니다. 따라서 preparedStatement를 써서 BIND변수를 사용하든지 그렇지 않든지 실행계획은 똑 같이 나옵니다.

이런 경우를 safe literal 이라고 합니다. 하지만,

select * from emp where deptno > :v1

위의 경우... :v1이 ...  1 일 경우와 1000일 경우는 분포도가 전혀 다릅니다. 1일 경우에는 
대부분의 deptno가 1보다는 크기 때문에... 분포도가 넓습니다. 즉, 인덱스를 타지 않는게
더 좋습니다.    하지만 deptno가 1000보다 큰 경우는 거의 없습니다. 즉, 분포도가 좁습니다. 이 경우 인덱스를 타야 합니다.

그러니가 히스토그램이라는 것은 이처럼 실재 :v1 값이 무엇이냐에 따라서 분포도가 달라지는 분포도 정보입니다. 어느 value가 어느 정도의 분포도를 가지고 있는지 그 정보를
히스토그램이라고 합니다.

문제는 BIND를 쓰면... :v1 이 1이 들어올지 1000이 들어올지에 무관하게 무조건 단 하나의 실행계획만을 만들고 일괄적으로 적용하다가 보니...  1000이 들어오는데도 불구하고 인덱스를 타지 않고 Full Scan하는 상황이 나올 수 있습니다.

즉, BIND 변수를 활용하며... 히스토그램 정보를 활용할 수 없습니다.

그렇다고면? prepatedStatement를 쓰지 말아야 할까요???  아닙니다. 써야 합니다.
위와 같이 불충분한 통계정보(히스토그램)이 없어서 실행계획을 잘못 수립해서 느려지는 경우는 어쩔 수 없는 현재의 옵티마이져의 한계로 보아야 합니다. 이 경우 똑똑한 사람이
힌트 등을 통해서 SQL에게 올바른 실행계획을 가르쳐줘야 합니다.

아주 특수한 경우 위의 이유로 튜닝을 위해 Statement를 쓰는 경우도 있습니다.
예를 들어... 성별이 남, 여 두가지일 경우... 남자가 전체의 2%이며 아주 극소수라고 가정하면.. 남자일 경우에는 인덱스르 타야하고... 여자일 경우 인덱스를 타지 말아야 합니다.
이 경우... 어차피 distinct 값의 종류가 딱 2가지 이므로... BIND를 쓰지 않아서
하드파싱을 해봤자... 2개의 SQL 종류 밖에는 나오지 않으므로 문제가 없습니다.

select * from user where sex = 'm'
select * from user where sex = 'f'

오라클의 Shared pool의 Library cache에는 위와 같이 딱 두종류의 SQL이 저장되어있어서 재사용되겠지요. 답변이 길었는데... 위의 이야기는 DBMS에 대해서 잘 아시는 분이 아니라면 어려울 수도 있습니다.

간단하게 이야기해서 실행계획을 비교해보십시오.

BIND변수를 썼을 때와 그렇지 않았을 때를... 분명이 위의 경우 실행계획이 전혀 다르게 나올겁니다. 그러니까. Unsafe literal 이죠.

따라서 ... BIND 쓰지 않았을 경우와 똑같이 실행계획이 나오도록 힌트를 사용해서 조정해 주십시오.

반드시 preparedStatement를 쓰시구요. 어쩌다 일주일에 한번쯤 돌리는 SQL이 아니라... 수시로 똑 같은 SQL이 반복적으로 계속 들어온다면 반드시 preparedStatement로 작성하셔야합니다. 즉, OLTP에서는 무조건 써야 합니다.


Technical Note/JAVA

Tomcat GC 에 대한 튜닝 방법
  1. 4cpu 일때 young, old 영역에 대해서 GC가 이루어지고 old 영역이 70일때 Full GC가 일어남
  2. tomcat/bin의 catalina.sh에 추가
    Tomcat GC 옵션

    JAVA_OPTS="-server -Xms1024m -Xmx1024m -XX:PermSize=64m -XX:MaxPermSize=128m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70 -Djava.awt.headless=true -Dcom.sun.management.jmxremote"

    option 중 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps는 10분 마다 GC 상태를 찍을 경우 필요함(기본은 필요 없음)


JVM Memory구성 옵션
  1. 메모리는 최대 Physical Memory의 1/2정도 설정시까지 문제 없음
    JVM Memory구성 옵션

    -Xms<Size> : 초기의 전체 힙 메모리의 크기 설정
    -Xmx<Size> : 최대 전체 힙 메모리의 크기 설정
    -XX:NewSize=<Size> : Young 영역의 메모리 크기 설정
    -XX:MaxNewSize=<Size> : 최대 Young영역 메모리 크기 결정
    -XX:NewRatio=<n>: Young영역의 메모리 크기를 결정하는 또 다른 방식 전체 메모리 크기에서 1/(n+1)만큼 Young영역 메모리로 할당된다.


먼저 알아둬야할 지식 Java HotSpot VM이 세가지 영역으로 힙을 분할해서 사용한다는 것. 이것을 기반으로 GC 정책을 세운다.
  1. Permanent space (반 영구적인 영역 ) : JVM 클래스와 method 객체가 사용한다
  2. Old object space (오래된 객체의 공간) : 잠시동안 있는 객체가 사용한다.
  3. New (young) object space (새로운 객체 공간 ) : 새로 생성한 객체가 사용한다.
    JVM 옵션

    -XX:+UseSerialGC: Young영역에 대한 GC. Young영역에서 살아남은 객체를 Old영역으로 복사하는 방식으로 동작. GC Thread가 1개임. Stop-the-world방식임. 이 옵션은 Java SE 5 이후 버전만 사용 가능.

    -XX:+UseParallelGC: Young영역에 대한 GC. GC Thread가 여러개 동작하지만, Stop-the-world방식. 즉 Application Thread를 멈추고, 여러 Thread가 GC를 수행함. Young영역이 100%사용하게 되었을때 동작한다. -XX:ParallelGCThreads 과 함께 사용 가능.

    -XX:+UseParNewGC : CPU가 여러개 있는 서버의경우에 유용

    -XX:+UseTLAB : Thread가 많고 객체 생성이 빈번한 경우, 공유메모리를 사용하는 것 보다는 스레드별로 메모리를 할당하는 것이 좋다 .16개 이상의 CPU를 장착한 시스템의 경우 쓰레드별로 객체 생성을 하기 이한 옵션

    -XX:+UseConcMarkSweepGC : Stop the world의 시간을 줄이기 이해서 작업스레드와 병행하여 GC정보를 수집한다.(Mark-sweep방식) -XX:ParallelGCThreads와 함께 사용하는게 좋을듯. CPU가 두개 이상이라면 사용하는게 좋을듯 하다.

    -XX:ParallelGCThreads=<n>: 병렬 가비지 컬렉터에서 가비지 컬렉션 스레드 수를 지정. 기본값은 CPU수와 동일하다.


Technical Note/JAVA
옵션의 종류
  • -X로 시작하는 옵션 : 비표준. JDK의 버전에 따라, 예고없이 변경될 수 있다.
  • -XX로 시작하는 옵션 : 안정화되지 않음(not stable). 일반적인 사용에 권장되지 않음. 이 옵션 또한 예고없이 변경될 수 있다.
    • Boolean옵션은 -XX:+<option> 으로 켜고, -XX:-<option>으로 끈다.
    • Numeric(숫자)옵션은 -XX:<option>=<number>로 설정한다. 사용되는 숫자에 m | M (mega), k | K (kilo), g | G (giga)를 포함할 수 있다.
    • String(문자)옵션은 -XX:<option>=<string>으로 설정한다. 보통 file, path, command list를 명시하기 위해 사용한다.
      optionsdescription
      -verbose:gcGC이벤트가 발생할때 마다 메시지를 reporting한다.
      -XmsN초기 memory allocation pool의 초기사이즈
      -XmxNmemory allocation pool의 최대 사이즈
      -XssNThread stack의 사이즈를 설정
      -XX:NewSize=2.125mnew generation영역의 기본크기
      -XX:MaxNewSize=sizeNew Generation(Eden + S0 + S1) 의 최대 크기를 설정한다.
      -XX:PermSizePermanent Generation의 기본사이즈
      -XX:MaxPermSize=64mPermanent Generation의 최대사이즈
      -XX:NewRatio=2old/new generation영역 크기의 비율
      -XX:SurvivorRatio=8eden/survivor영역 크기의 비율
      -XX:ThreadStackSize=512Thread Stack의 크기
      -XX:-PrintGCGC가 일어날때 message를 출력한다.
      -XX:-PrintGCDetailsGC에 관해 더 자세한 메시지를 출력한다.
      -XX:-PrintGCTimeStampsGC의 timestamp를 출력한다.
JVM Tuning Example

JAVA_OPTS=-Xms1024m -Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=256m -XX:NewSize=512m -XX:MaxNewSize=512m -XX:SurvivorRatio=4 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

HeapSize1024M
New Generation512M
Eden348M
Survivor187M
Survivor287M
Old Generation256M
Permanent256M


Technical Note/JAVA

자바에서는 개발자가 프로그램에서 별도로 메모리 관리를 하지 않는다. 이것은 C/C++에서는 없던 획기적인 것이었다. 자바의 메모리 관리 개념은 개발자를 열광케 하고, 나아가 자바가 세상을 지배하는 언어가 되는데 핵심 역할을 담당하였다 그러나 애석하게도 자바에서는 여전히 메모리 문제가 발생하고있다. C/C±±처럼 빈번하지는 않지만 종종 OutOfMemoryError는 발생하고 있으며, 이러한 메모리 부족 에러는 운영자나 개발자가 가장 껄끄러워하는 문제 중에 하나로 인식되어있다.

자바의 메모리 부족 유형

Out Of Memory Error가 발생하는 원인을 보면 크게 단지 (1)메모리가 부족할 때 (2)Heavy 서비스가 수행되면서 혹은 (3) 메모리 LEAK에 의한 메모리 부족 때문으로 구분할 수 있다. 
(1)의 단지 메모리가 부족하다는 것은 설정된 메모리량이 요구되는 메모리량에 비해 부족하다는 것이다. 프로그램이나 기타 오류가 아닌 단지 값이 적게 설정되었다는 것을 의미하기 때문에 통상적으로는 -Xmx값을 늘려줌으로써 해결되는 것이 보통이다. 실제로는 의외로 JVM 시작시 메모리 옵션을 빠뜨리는 경우가 많다. 혹은 -Xmx256과 같이 메모리 단위(원하는 설정-Xmx256m)를 빠트리는 경우도 있다. 물론 옵션을 잘못 기술하는 것은 단순한 실수이지만, 자바 힙 메모리에 대한 경험이 전혀 없어서 단위 시간당 서비스 호출건수(Service Arrival Rate)에 비해 턱 없이 적은 량의 메모리를 지정하는 경우도 종종 있다.
SUN/HP JVM에서는 perm 영역이 작아서 발생하는 경우가 있다. 메모리의 perm영역에는 클래스 코드 등이 저장되는 공간으로 사용되는 클래스가 많은 경우에는 사이즈를 늘려 주어야 한다. 
(2)두번째로 OutOfMemoryError는 응용서비스(ex Web Request 처리 로직)가 순간적으로 메모리를 과도하게 사용함으로써 발생할 수 있다.
데이터 베이스에서 너무 많은 데이터를 로딩하거나 검색 조건이 잘못되어 검색서버에서 너무 많은 텍스트를 조회하는 경우가 해당된다. 또 다른 경우로 과거에 upload/download프로그램에서 자주 발견되던 것으로 byte[] buffer에서 buffer의 크기를 너무 크게 설정함으로써 연속공간 부족 현상(IBM JVM)이 발생하는 경우나, 동시에 여러 개의 요청이 발생하여 단위 시간당 절대 사용량이 많아 OuOfMemory발생하는 경우를 생각할 수 있다. 물론 후자의 경우는 application이 정상적으로 메모리를 많이 사용하는지 단지 프로그램 오류인지는 판별하기 어려울 수도 있다. 
(3)마지막으로 MEMORY LEAK이 발생하여 메모리가 부족해지는 경우이다. 보다 정확하게 자바에서 메모리 LEAK은 로직적으로 사용되지 않는 객체가 GC될 수 없는 strong reference에 의해 참조되어 메모리 사용량이 증가하는 상태를 말한다. 대부분 적절한 반환을 거치지 않아 발생한다. 대표적으로 JDBC관련 클래스를 사용하고 close()를 호출하지 않게 되어 발생하는 경우를 생각할 수 있다(물론 JDBC에 따라서 혹은 Web Application Server에 따라서 메모리 Leak이 발생하지 않을 수 도 있다.) 
메모리 LEAK의 다른 사례로 Cache의 구현 로직에 문제가 있는 경우이다. CACHE에서 불필요한 데이터의 제거 로직이 정상적으로 동작하지 않는 경우 OutOfmemoryError가 발생할 수있다. 포괄적인 의미에서 Connection Pool에서 불필요한 Connection이 증가하거나, Http Session에서 데이터가 증가하는 현상 또한 이범주에 포함 시킬수 있을 것이다.

OutOfMemoryError 발생시 원인 분석

화면이나 프로그램 로그에서 OutOfMemoryError를 보게 된다면 어떤 경우에 발생하는지를 정확히 판별해야 한다.
단지 설정이 잘못된 경우라면 통상적으로 JVM startup 혹은 startup이후 짧은 시간내에 OutOfMemory가 발생하게된다.
따라서 서버를 start 할 때 OutOfMemoryError를 보게 된다면 옵션 설정을 먼저 의심해 보아야 한다. 
그러나PERM영역이 부족한 경우에는 약간 시간을 두고 발생하게 된다. HEAP메모리가 부족하지 않는데 OutOfMemoryError가 발생하면서 SUN/HP JVM이라면 PERM영역이 부족하지 않은지를 먼저 확인해야 할 것이다.

만약 (2)Heavy Load Application이 수행되어 OutOfMemory가 발생한다면 어떤 현상이 관찰될까? HEAP Memory 그래프를 보면 갑자기 메모리 사용량이 급격히 증가하고 GC가 빈번해지는 현상이 관찰되는 것이 일반적이다. 시간에 따라 메모리 사용량이 증가하지 않고 어느 순간에 메모리 사용량이 증가하고 GC가 빈번해 지는 현상이 발생한다면 특정 Application이 메모리를 과도하게 사용함을 추정할 수 있다. 물론 Active Service가 증가한 이후에 Memory사용량이 증가하고 GC가 빈번해지면 인과관계(메모리가 부족해서 Active Service가 증가 했는지 아니면 Active Service가 증가해서 OutOfMemory가 발생했는지)가 약간 불분명할 수는 있다. 단 필자는Active Service가 증가하여 OutOfMemory가 발생하는 경우는 거의 보지 못했다.

IBM JVM인 경우에 HEAP에 여유가 충분하고 GC가 빈번해지지도 않으면서 갑자기 OutOfMemoryError가 발생한 경우가 있는데 이때는 fragmentation에 의한 연속공간부족을 의심할 필요가 있다

마지막으로 Memory Leak이 발생하여 메모리 부족이 발생하면은 Heap Usage그래프가 시간에 따라 증가되는 현상으로 나타난다. 장시간의 메모리 사용량의 변화를 관찰하면 메모리 릭이 있는지를 확인할 수 있다. 일반적으로 서버 기동후 OutOfMemory가 발생하기 까지 시간이 단 몇시간인 경우도 있으나 한 달이 넘는 경우도 있다.

제니퍼와 OutOfMemoryError 해결

단지 설정이 잘못되거나 너무 적은 메모리를 설정한 경우에는 설정을 수정함으로써 해결하면 OK이지만 특정 Application에 의한 OutOfmemoryError나 MEMORY LEAK이 발생하는 경우에는 해결을 위해 보다 논리적 접근이 필요하다.

특정 application에 의한 불특정 시점에 OutOfMemoryError이 발생하는 것이 확인된다며 어떤 application이 Memory Error를 발생하는지를 찾아야 한다. 만약 해당 Application이 upload나 download와 관련있는 프로그램이라면 데이터를 제어하기 위한 버퍼 사이즈를 먼저확인하고 단순히 DB를 사용하는 프로그램이라면 DB Access 데이터 량을 확인하는 것이 먼저이다. 제니퍼는 서비스 수행중 OutOfMemoryError가 발생하면 ERROR을 발행하고 에러 정보를 저장한다.
통상적으로 메모리를 과도하게 사용하는 서비스가 수행되면 순간적으로 해당 인스턴스의 응답시간/CPU사용량등이 증가하게되며 XVIEW상에서 모든 프로그램의 응답시간이 급격하게 증가하는 현상을 관찰할 수있는데 이때 가장 높은 곳(응답시간이 길거나/CPU사용량이 높음)에 위치하는 것이 문제의 Application인 경우가 많다. 따라서 일자별 XView 데이터 조회를 통해서 해당 시간대를 면밀히 관찰하면 쉽게 찾을 수 있다.

MEMORY LEAK 이것은 원만큼 자바 튜닝에 공력이 붙지 않으면 해결하기 힘들다. 
긴 시간이 필요한데다가 개발 서버에서는 좀처럼 재연되지 않기 때문이다. 
일단 메모리 릭을 확신하기 위해서는 제일 먼저 시간에 따라 메모리가 증가 한다는 것을 감지 하는 것이 중요하다
따라서 짧은 시간의 메모리 사용량이 아닌 하루의 전체 HEAP 메모리 사용량 변화를 확인할 수 있는 화면이 필수이다.(제니퍼 매뉴얼 참조)

IBM JVM을 사용하는 시스템이라면 HEAP DUMP를 통해 쉽게 원인을 찾을 수 있지만
SUN/HP에서는 정말 어려운 문제이다. 특정 application을 여러 번 호출하고 메모리 변화를 확인하는 방식은 개발서버에서는 가능할지는 몰라도 운영 환경에서는 사용이 불가능하다 마찬가지고 -Xrunhprof:heap 옵션을 이용한 HEAP분석 또한 현실적으로 운영환경에서 사용하기 어렵다. APM에서 모든 메모리 릭을 해결하기 위한 기능은 운영환경(production)에서 사용 가능해야 한다.

이런 조건하에서 제니퍼는 메모리 LEAK을 추적할 수 있는 몇가지 기능이 준비되어있다.
일단 제니퍼는 JDBC 자원 미반환은 거의 완벽(일부 특수한 상황은 예외)하게 검출한다. MEMORY LEAK이 발생하면 가장먼저 JDBC 자원 미반환 코드를 수정할 것을 권고한다.

그리고 제니퍼는 Collection/Map클래스사이즈 모니터링 기능을 제공하는데 이는 제니퍼 만이 제공하는 독특한 기능이다. 나는 다수의 메모리 LEAK을 IBM JVM /HEAP DUMP분석을 통해 해결하면서 MEMORY LEAK이 대부분 Collection/Map 계열의 클래스에서 발생한다는 것을 알게 되었다. Cache/Pool 컴포넌트는 통상 내부에 Collection/Map형태의 클래스에 데이터를 저장하고 이것이 잘못되어 메모리 릭을 유발하는 경우가 가장 많았다. 이것을 반대로 보면 어떤 Collection/Map의 elements count가 증가하는지를 모니터링 할 수 있다면 상당히 많은 MEMORYLEAK을 잡을 수 있을 것이다. 제니퍼는 이를 통해 상당수의 실 싸이트에서 타사 제품이 해결하지 못한 메모리 LEAK을 해결한바 있다.

또한 제니퍼 3.2에는 새로운 기능의 Collection 모니터링 기능이 추가되었다. 
지정한 jar파일들에서 모든 static 변수에 선언된 Collection/Map을 검색하고 이것을 
구성파일에 설정함으로써 element count을 추적하는 기능이다.(제니퍼 사용자 메뉴얼 참조)

Technical Note/JAVA

아마도 윈도우에서 (리눅스나 Unix계열도 마찬가지로) javaw.exe로 실행 시킨 프로그램이나 EclipseNetBeans등의 개발 도구에서 내부 콘솔로 띄운 프로그램 혹은 서비스(데몬)로 띄운 프로그램의 쓰레드 덤프를 얻고 싶은데, 어떻게 해야 할지 몰라 당황한 적이 있을 것이다.

이미 떠있는 JVM의 스택 트레이스(Stack Trace 혹은 쓰레드 덤프 Thread Dump)를 뜨기 위한 개발 도구가 생겼다.

jstack

쓰레드 덤프 하기

 

이 툴은 아주 효과 만점이다.

kill -3 자바프로세스번호 하면서 고생하던 것을 쉽게 해결해준다.

 

치면 이렇게 나온다.

 

Usage:
    jstack [-l] <pid>
        (to connect to running process)
    jstack -F [-m] [-l] <pid>
        (to connect to a hung process)
    jstack [-m] [-l] <executable> <core>
        (to connect to a core file)
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
        (to connect to a remote debug server)

Options:
    -F  to force a thread dump. Use when jstack <pid> does not respond (process is hung)
    -m  to print both java and native frames (mixed mode)
    -l  long listing. Prints additional information about locks
    -h or -help to print this help message

 

강제로, native영역과 함께, 특히 deadlock 여부까지!! 오호! 굿

 

테스트 해보자. 물론 리눅스 환경에서. (윈도우도 되겠지만)

/usr/local/jdk6/bin/jps v             // ps -ef | grep java와 같다.

 /usr/local/jdk6/bin/jstack 21720 > td.txt    // java 프로세스 넘버주면, 파일로 덤프가 가능하다.

 

/usr/local/jdk6/bin/jstack -l 21720 > td.txt   // 이렇게 하면,  deadlock까지 체크!

 


Unix/Linux 계열은 Java 5부터 (실질적으로는 JDK 1.4.2_11 도 포함하고 있다고 한다) jstack 이라는 프로그램이 JDK에 포함되었다.
하지만, Windows 용 JDK는 Java 6 부터 포함되었다.

이것은 이미 떠 있는 JVM 프로세스에서 쓰레드 덤프를 뜰 수 있게 해주는 유틸리티이다.
문제는 사실, Unix/Linux 계열에서는 이런 도구가 없어도 어느정도 kill -3 명령으로 쓰레드 덤프 뜨는게 가능 했고, 정작 javaw.exe나 혹은 서비스로 뜬 JVM의 쓰레드 덤프를 뜰 수 있는 방법이 거의 없는(있기는 있다) 윈도우용은 Java 6 이상이어야만 사용 가능하다는 점이다.

아무튼....
먼저 jps 라는 명령으로 JVM의 프로세스 번호를 얻어온다.
C:\Java\jdk1.6.0\bin>jps --help
illegal argument: --help
usage: jps [-help]
       jps [-q] [-mlvV] [<hostid>]

Definitions:
    <hostid>:      <hostname>[:<port>]

C:\Java\jdk1.6.0\bin>jps -v
3280 Jps -Dapplication.home=C:\Java\jdk1.6.0 -Xms8m
4740 startup.jar -Xms40m -Xmx256m

그리고는 jstack 명령에 PID를 지정해주면 해당 PID를 가진 JVM의 Stack Trace를 얻게 된다.
C:\Java\jdk1.6.0\bin>jstack 4740 > st.txt

/usr/local/jdk6/bin/jstack -l 21720 > td.txt   // 이렇게 하면,  deadlock까지 체크!


Stack Trace는 당연히 st.txt 파일에 저장되어 있다.

jstack에 대한 소개는 Alan Bateman의 jstack이라는 블로그 글에서 보았다.

Stack Trace

그렇다면 Windows 에서 Java 5, 1.4 등의 Stack Trace를 얻을 수 있는 방법은 없을까? 있다.
가장 간단한 방법은 Stack Trace라는 프로그램을 이용하는 것이다. 원래는 상용이지만 Java Web Start로 설치하면 회사에서도 무료로 이용하게 되어 있다.

jconsole

두번째 방법으로 Java 5의 jconsole을 이용하는 방법이 있다.
Java 어플리케이션을 실행할 때 시스템 프라퍼티(System property)로 com.sun.management.jmxremote를 지정해서 실행하면 된다.
다음은 eclipse.ini 파일에 -vmargs 의 부가 옵션으로 -Dcom.sun.management.jmxremote를 지정해서 Eclipse를 실행한 뒤(Windows XP라면 방화벽에서 예외로 지정해야 한다)에 jconsole로 본 것이다. jconsole은 그냥 jconsole.exe를 실행하기만 하면 된다.

* jconsole을 실행하여, 모니터링할 JVM을 선택한다.


* 해당 JVM의 쓰레드 상태를 살펴본다.



1 2 3 4
블로그 이미지

zzikjh