How to find bottleneck in J2EE application 작성자 : 자바스터디네트워크조대협 (bcho@bea.com) J2ee application 을운영하다보면, 시스템이극도로느려지거나, 멈춰버리는현상이생기고는한데, 분명히개발하면서테스트할때는문제가없었는데, 왜이런일이생기고, 어떻게대처해야하는지에대해서알아보도록하자. 일반적으로 J2ee application 을서비스하기위해서는아래와같은구조를가지게된다. client Web Server User Application WAS OR Servlet Engine RDBMS N/W JVM < 그림 1. 일반적은 J2ee application 의구조 > J2ee application 의동작에필요한구성요소를나눠보면위와같이 Network, User Application ( 이하 User AP), WAS 또는 Servlet Engine( 이하통칭해서 WAS),JVM 과 RDBMS 이렇게크게다섯가지조각으로나뉘어진다. 물론 JCA 를이용해서 Legacy 와연결할수도있고, RMI/CORBA 를이용하여다른 Architecture 를구현할수는있으나, 이강좌는어디까지나일반론을설명하고자하는것임으로범위에서는제외하겠다. 1. Hang up 과 slow down 현상의정의 먼저용어를정의하도록하자.. 시스템이느려지거나멈추는현상에대해서아래와같이용어를정의하자
- Hang up : Server Instance 는실행되고있느나, 아무런응답이없는상황 ( 멈춤상태 ) - Slowdown : Server Instance 의 response time 이아주급격히떨어지는상태 ( 느려짐 ) 이 Hangup 과 slowdown 현상은, 대부분이그림 1 에서설명한다섯가지요소중하나이상의병목으로인해서발생한다. 즉, 이병목구간을발견하고, 그구간을제거하면정상적으로시스템을운영할수있게되는것이다. 2. Slow down analysis in WAS & User AP 1. WAS 의기본구조 이병목구간을발견하는방법에앞서서, 먼저 WAS 시스템의기본적인내부구조를이해할필요가있다. * Thread Pooling < 그림 2. WAS 시스템의구조 > < 그림 2> 는일반적인 WAS 의구조이다.
WAS 는 Client 로부터 request 를받아서, 그 Request 의내용을분석한다 JMS 인지, HTTP, RMI request 인지를분석한후 (Dispatcher) 그내용을 Queue 에저장한다. Queue 에저장된내용은 WAS 에서 Request 를처리할수있는 Working Thread 들이있을때각각의 Thread 들이 Queue 에서 Request 를하나씩꺼내서각각의 Thread 들이그기능을수행한다. 여기서우리가주의깊게봐야하는것은 Thread pooling 이라는것인데. Thread 를미리만들어놓고, Pool 에저장한체로필요할때그 Thread 를꺼내서사용하는방식이다. (Connection Pooling 과같은원리이다.) WAS 에서는일반적으로업무의성격마다이 Thread Pool 을나누어서만들어놓고사용을한다. 즉예를들어서금융시스템의예금시스템과보험시스템이하나의 WAS 에서동작할때, 각각의업무를 Thread Pool 을나누어서분리하는방식이다. 이방식을사용하면업무의부하에따라서각 Thread Pool 의 Thread 수를조정할수있으며, 만약에 Thread 가모자르거나 deadlock 등의장애사항이생기더라도그것은하나의 Thread Pool 에만국한되는이야기이기때문에, 다른업무에영향을거의주지않는다. 2. Thread Dump 를통한 WAS 의병목구간분석 위에서살펴봤듯이, WAS 는기본적으로 Thread 기반으로작동하는 Application 이다. 우리가 hang up 이나 slow down 은대부분의경우가 WAS 에서현상이보이는경우가많다. (WAS 에원인이있다는소리가아니라.. 다른요인에의해서라도 WAS 의처리속도가느려질수있다는이야기다.) 먼저이 WAS 가내부적으로어떤 Application 을진행하고있는지를알아내면병목구간에한층쉽게접근할수가있다. 1) What is thread dump? 이를위해서 JVM 에서는 Java Thread Dump 라는것을제공한다. Thread Dump 란 Java Application 이작동하는순간의 X-Ray 사진, snapshot 을의미한다. 즉이 Thread dump 를연속해서수번을추출한다면, 그당시에 Application 이어떻게동작하고진행되고있는가를살펴볼수있다. Thread dump 를추출하기위해서는 - Unix 에서는 kill 3 pid - Windows 계열에서는 Ctrl + break
를누르면 stdout 으로 thread dump 가추출된다. Thread dump 는 Application 을구성하고있는현재구동중인모든 Thread 들의각각의상태를출력해준다. "ExecuteThread: '42' for queue: 'default'" daemon prio=5 tid=0x3504b0 nid=0x34 runnable [0x9607e000..0x9607fc68] at java.net.socketinputstream.socketread(native Method) at java.net.socketinputstream.read(socketinputstream.java:85) at oracle.net.ns.packet.receive(unknown Source) at oracle.net.ns.netinputstream.getnextpacket(unknown Source) at oracle.net.ns.netinputstream.read(unknown Source) at oracle.net.ns.netinputstream.read(unknown Source) at oracle.net.ns.netinputstream.read(unknown Source) at oracle.jdbc.ttc7.marengine.unmarshalub1(marengine.java:730) at oracle.jdbc.ttc7.marengine.unmarshalsb1(marengine.java:702) at oracle.jdbc.ttc7.oall7.receive(oall7.java:373) at oracle.jdbc.ttc7.ttc7protocol.dooall7(ttc7protocol.java:1427) at oracle.jdbc.ttc7.ttc7protocol.fetch(ttc7protocol.java:911) at oracle.jdbc.driver.oraclestatement.doexecutequery(oraclestatement.java:1948) at oracle.jdbc.driver.oraclestatement.doexecutewithtimeout(oraclestatement.java:2137) at oracle.jdbc.driver.oraclepreparedstatement.executeupdate(oraclepreparedstatement.java:404) at oracle.jdbc.driver.oraclepreparedstatement.executequery(oraclepreparedstatement.java:344) at weblogic.jdbc.pool.preparedstatement.executequery(preparedstatement.java:51) at weblogic.jdbc.rmi.internal.preparedstatementimpl.executequery(preparedstatementimpl.java:56) at weblogic.jdbc.rmi.serialpreparedstatement.executequery(serialpreparedstatement.java:42) Thread name Program stack of this thread Thread id (signature) < 그림 3-2. Thread dump > Thread Status Sun JVM 그림 3-2 는전체 Thread dump 중에서하나의 Thread 를나타내는그림이다. Thread Dump 에서각각의 Thread 는 Thread 의이름과, Thread 의 ID, 그리고 Thread 의상태와, 현재이 Thread 가실행하고있는 Prorgam 의스택을보여준다. - Thread name 각쓰레드의이름을나타낸다. WAS 나 Servlet Engine 에따라서는이이름에 Thread Queue 이름등을배정하는경우가있다. - Thread ID 쓰레드의 System ID 를나타낸다. 이강좌나중에이 ID 를이용해서각 Thread 별 CPU 사용률을추적할수있다. - Thread Status
매우중요한값중의하나로각 Thread 의현재상태를나타낸다. 일반적으로 Thread 가사용되고있지않을때는 wait 를, 그리고사용중일때는 runnable 을나타내는게일반적이다. 그외에, IO Wait 나 synchronized 등에걸려있을때 Wait for monitor entry, MW (OS 별 JVM 별로틀림 ) 등의상태로나타난다. - Program stack of thread 현재해당 Thread 가어느 Class 의어느 Method 를수행하고있는지를나타낸다. 이정보를통해서현재 WAS 가어떤작업을하고있는지를유추할수있다. 2) How to analysis thread dump 앞에서 Thread dump 가무엇이고, 어떤정보를가지고있는지에대해서알아봤다. 그러면이 Thread dump 를어떻게분석을하고, System 의 Bottle neck 을찾아내는데이용할지를살펴보기로하자. System 이 Hangup 이나 slowdown 에걸렸을때, 먼저 Thread dump 를추출해야한다. 이때한개가아니라 3~5 초간격으로 5 개정도의 dump 를추출한다. 추출한여러개의 dump 를연결하면각 Thread 가시간별로어떻게변하고있는지를판별할수있다. 먼저각각의 Thread 덤프를비교해서보면, 각각의 Thread 는적어도 1~2 개의덤프내에서연속된모습을보여서는안되는게정상이다. 일반적으로 Application 은내부적으로매우고속으로처리되기때문에, 하나의 Method 에 1 초이상머물러있는다는것은거의있을수없는일이다. Thread Dump 의분석은각각의 Thread 가시간이지남에따라서진행되지않고멈춰있는 Thread 를찾는데서부터시작된다. 예를들어서설명해보자. 아래 < 그림 3-3> 의프로그램을보면 MY_THREAD_RUN() 이라는메소드에서부터 MethodA() MethodB() MethodC() 를차례로호출하는형태이다. MYTHREAD_RUN(){ call methoda(); } methoda(){ //dosomething(); call methodb(); } methodb(){ //call methodc(); } methodc(){ // dosomething for(;;){// infinite loop } } < 그림 3-3. sample code >
이프로그램을수행하는중에처음부터 Thread Dump 를추출하면대강다음과같은형태일것임을예상할수있다. 함수호출에따라서 Stack 이출력되다가 의단계즉 MethodC 에서무한루프에빠지게되면더이상프로그램이진행이없기때문에똑같은덤프를유지하게된다. MYTHREAD runnable at MYTHREAD_RUN() : MYTHREAD runnable at methoda() at MYTHREAD_RUN() : MYTHREAD runnable at methodb() at methoda() at MYTHREAD_RUN() : MYTHREAD runnable at methodc() at methodb() at methoda() at MYTHREAD_RUN() : MYTHREAD runnable 이덤프모양이반복됨 at methodc() at methodb() at methoda() at MYTHREAD_RUN() : 우리는이덤프만을보고 methodc 에서무엇인가잘못되었음을유추할수있고, methodc 의소스코드를분석함으로써문제를해결할수있다. 그렇다면이제부터 Slow down 과 Hang up 현상을유발하는일반적인유형과그 Thread Dump 의모양에대해서알아보도록하자. - CASE 1. lock contention 잘알다싶이 Java 는 Multi Threading 을지원한다. 이경우공유영역을보호하기위해서 Synchronized method 를이용하는데, 우리가흔히들말하는 Locking 이이것이다. 예를들어 Thread1 이 Synchronized 된 Method A 의 Lock 을잡고있는경우, 다른쓰레드들은그 Method 를수행하기위해서, 앞에서 Lock 을잡은쓰레드가그 Lock 을반환하기를기다려야한다. 그림 3-4 Thread 1 Thread 2
Synchronized MethodA Thread 3 Thread 4 Wait for Lock < 그림 3-4. Thread 간에 Lock 을기다리는형태 > 만약에이 Thread 1 의 MethodA 의수행시간이아주길다면 Thread 2,3,4 는마냥이수행을아주오랜시간기다려야하고, 다음 2 번이 Lock 을잡는다고해도 3,4 번 Thread 들은 1 번과 2 번쓰레드가끝난시간만큼의시간을누적해서기다려야하기때문에, 수행시간이매우느려지는현상을겪게된다. 이처럼여러개의 Thread 가하나의 Lock 을동시에획득하려고하는상황을 Lock Contention 이라고한다. Threads Thread dump Lock 을기다리는쓰레드 (Thread 2) Lock 을잡고있는쓰레드 (Thread 1) Time < 그림 3-5. Lock Contention 상황에서 Thread 의시간에따른진행상태 > 이런상황에서 Thread Dump 를추출해보면 < 그림 3-6> 과같은형태를띠게된다. "ExecuteThread: '12' for queue: 'weblogic.kernel.default'" daemon prio=10 tid=0x0055ae20 nid=23 lwp_id=3722788 waiting for monitor entry [0x2fb6e000..0x2fb6d530] at sun.misc.launcher$appclassloader.loadclass(launcher.java:258) - waiting to lock <0x38819a18> (a sun.misc.launcher$appclassloader) at java.lang.classloader.loadclass(classloader.java:292) - locked <0x38db9ea8> (a weblogic.utils.classloaders.genericclassloader) at java.lang.classloader.loadclass(classloader.java:292) - locked <0x38f520f8> (a weblogic.utils.classloaders.genericclassloader) at java.lang.classloader.loadclass(classloader.java:292) - locked <0x3941af18> (a weblogic.utils.classloaders.genericclassloader) at java.lang.classloader.loadclass(classloader.java:292) - locked <0x3941b6b0> (a weblogic.utils.classloaders.changeawareclassloader)
at java.lang.classloader.loadclass(classloader.java:255) : at org.apache.xerces.jaxp.saxparserfactoryimpl.newsaxparser(unknown Source) at org.apache.axis.utils.xmlutils.getsaxparser(xmlutils.java:252) - locked <0x329fcf50> (a java.lang.class) "ExecuteThread: '13' for queue: 'weblogic.kernel.default'" daemon prio=10 tid=0x0055bde0 nid=24 lwp_id=3722789 waiting for monitor entry [0x2faec000..0x2faec530] at org.apache.axis.utils.xmlutils.getsaxparser(xmlutils.java:247) - waiting to lock <0x329fcf50> (a java.lang.class) at org.apache.axis.encoding.deserializationcontextimpl.parse(deserializationcontextimpl.java:239 "ExecuteThread: '14' for queue: 'weblogic.kernel.default'" daemon prio=10 tid=0x0061d680 nid=25 lwp_id=3722790 waiting for monitor entry [0x2fa6b000..0x2fa6b530] at org.apache.axis.utils.xmlutils.releasesaxparser(xmlutils.java:283) - waiting to lock <0x329fcf50> (a java.lang.class) at org.apache.axis.encoding.deserializationcontextimpl.parse(deserializationcontextimpl.java:254) "ExecuteThread: '15' for queue: 'weblogic.kernel.default'" daemon prio=10 tid=0x0061dc20 nid=26 lwp_id=3722791 waiting for monitor entry [0x2f9ea000..0x2f9ea530] at org.apache.axis.utils.xmlutils.releasesaxparser(xmlutils.java:283) - waiting to lock <0x329fcf50> (a java.lang.class) at < 그림 3-6. Lock Contention 에서 Lock 을기다리고있는상황의 Thread Dump> 그림 3-6 의덤프를보면 12 번 Thread 가 org.apache.axis.utils.xmlutils.getsaxparser 에서 Lock 을잡고있는것을볼수있고, 36,15,14 번쓰레드들이이 Lock 을기다리고있는것을볼수있다. 해결방안이런 Lock Contention 상황은 Multi Threading 환경에서는어쩔수없이발생하는상황이기는하지만, 이것이문제가되는경우는 Synchronized Method 의실행시간이불필요하게길거나, 불필요한 Synchronized 문을사용했을때발생한다. 그래서이문제를해결하기위해불필요한 Sychronized Method 의사용을자제하고, Synchronized block 안에서의최적화된 Algorithm 을사용하는것이필요하다. - CASE 2. dead lock 이 Locking 으로인해서발생할수있는또다른문제는 dead Lock 현상이다. 두개이상의쓰레드가서로 Lock 을잡고기다리는 환형대기조건 이성립되었을때, 서로 Lock 이풀리지않고무한정대기하는현상을이야기한다.
Time 그림 3-7 을보면 Thread1 은 MethodA 를수행하고있는데, sychronized methodb 를호출하기전에 Thread2 가 methodb 가끝나기를기다리고있다. 마찬가지로 Therad2 는 Thread3 가수행하고있는 methodc 가끝나기를기다리고있고, methodc 에서는 Thread1 에서수행하고있는 methoda 가끝나기를기다리고있다. 즉각각의메소드들이서로끝나기를기다리고있는 환형대기조건 이기때문에이 Application 의 3 개의쓰레드들은프로그램이진행이되지않게된다. Thread 1 Synchronized MethodA Waitting Thread 3 Synchronized MethodC Waitting Thread 2 Synchronized MethodB Waitting sychronized methoda(){ call methodb(); } sychronized methodb(){ call methodc(); } sychronized methodc(){ call methoda(); } < 그림 3-7. 환형대기조건에의한 deadlock > 이러한 환형대기조건 에의한 deadlock 은 Thread Dump 를추출해보면 < 그림 3-8> 과같은패턴을띠우고있으며시간이지나도풀리지않는다. Threads Thread dump
서로 Lock 을잡고있음 < 그림 3-8. Deadlock 이걸렸을때시간진행에따른 Thread 의상태 > "ExecuteThread-6" (TID:0x30098180, sys_thread_t:0x39658e50, state:mw, native ID:0xf10) prio=5 at oracle.jdbc.driver.oraclestatement.close(oraclestatement.java(compiled Code)) at weblogic.jdbc.common.internal.connectionenv.cleanup(connectionenv.java(compiled Code)) at weblogic.jdbc.common.internal.connectionpool.release(connectionpool.java(compiled Code)) at weblogic.jdbcbase.pool.connection.close(connection.java(compiled Code)) at sis.ao.svao0400r.svao0400rq.mainprocess(svao0400rq.java(compiled Code)) at sis.ao.svao0400r.svao0400rq.doget(svao0400rq.java(compiled Code)) at javax.servlet.http.httpservlet.service(httpservlet.java(compiled Code)) at javax.servlet.http.httpservlet.service(httpservlet.java(compiled Code)) at weblogic.servlet.internal.servletstubimpl.invokeservlet(servletstubimpl.java(compiled Code)) : "ExecuteThread-8" (TID:0x30098090, sys_thread_t:0x396eb890, state:mw, native ID:0x1112) prio=5 at oracle.jdbc.driver.oracleconnection.commit(oracleconnection.java(compiled Code)) at weblogic.jdbcbase.pool.connection.commit(connection.java(compiled Code)) at sis.as.svas0900e.svas0902es.mainprocess(svas0902es.java(compiled Code)) at sis.as.svas0900e.svas0902es.doget(svas0902es.java(compiled Code)) at javax.servlet.http.httpservlet.service(httpservlet.java(compiled Code)) at javax.servlet.http.httpservlet.service(httpservlet.java(compiled Code)) at weblogic.servlet.internal.servletstubimpl.invokeservlet(servletstubimpl.java(compiled Code)) : ( 중략 ) sys_mon_t:0x39d75b38 infl_mon_t: 0x39d6e288: oracle.jdbc.driver.oracleconnection@310bc380/310bc388: owner "ExecuteThread-8" (0x396eb890) 1 entry 1) Waiting to enter: "ExecuteThread-10" (0x3977e2d0) "ExecuteThread-6" (0x39658e50) sys_mon_t:0x39d75bb8 infl_mon_t: 0x39d6e2a8: \ oracle.jdbc.driver.oraclestatement@33aa1bd0/33aa1bd8: owner "ExecuteThread-6" (0x39658e50) 1 entry 2) Waiting to enter: "ExecuteThread-8" (0x396eb890
IBM AIX 4.3.3 JVM 1.3.0 IBM AIX 의 JVM 의경우에는 Lock 의정보가각 Thread 의 Stack 에나타나는것이아니라. 별도로 Thread 별 Locking 정보를따로나타내주기때문에.. Lock 정보를별도로확인해야한다. < 그림 3-9. Deadlock 이걸린 IBM AIX Thread Dump > DeadLock 의검출은 Locking Condition 을비교함으로써검출할수있으며, 최신 JVM (Sun 1.4 이상등 ) 에서는 Thread Dump 추출시만약 Deadlock 이있다면해당 Deadlock 을찾아주는기능을가지고있다. 그림 3-9 를보자. IBM AIX 의 Thread 덤프이다. 1) 항목을보면현재 8 번쓰레드가잡고있는 Lock 은 10 번과 6 번쓰레드가기다리고있음을알수있으며. Lock 은 OracleConnection 에의해서잡혀있음을확인할수있다. ( 아래 ) sys_mon_t:0x39d75b38 infl_mon_t: 0x39d6e288: oracle.jdbc.driver.oracleconnection@310bc380/310bc388: owner "ExecuteThread-8" (0x396eb890) 1 entry 1) Waiting to enter: "ExecuteThread-10" (0x3977e2d0) "ExecuteThread-6" (0x39658e50) 그림 3-9 의 2) 번항목을보면이 6 번쓰레드는 OracleStatement 에서 Lock 을잡고있는데, 이 Lock 을 8 번쓰레드가기다리고있다. ( 아래 ) sys_mon_t:0x39d75bb8 infl_mon_t: 0x39d6e2a8: \ oracle.jdbc.driver.oraclestatement@33aa1bd0/33aa1bd8: owner "ExecuteThread-6" (0x39658e50) 1 entry 2) Waiting to enter: "ExecuteThread-8" (0x396eb890 결과적으로 6 번과 8 번은서로 Lock 을기다리고있는상황이되어 Lock 이풀리지않는 Dead Lock 상황이된다. 해결방안 Dead Lock 의해결방안은서로 Lock 을보고있는것을다른 Locking Object 를사용하거나 Lock 의방향 (Synchronized call 의방향 ) 을바꿔줌으로써해결할수있다.
User Application 에서발생한경우에는 sychronized method 의호출순서를 환형대기조건 이생기지않도록바꾸도록하고. 위와같이 Vendor 들에서제공되는코드에서문제가생긴경우에는 Vendor 에패치를요청하도록하여해결한다. - CASE 3. wait for IO response 다음으로많이볼수있는패턴은각각의 Thread 들이 IO Response 를기다리는데 IO 작업에서 response 가느리게와서시스템처리속도가느려지는경우가있다. Threads Thread dump DB 로부터응답을기다리고있음 Time < 그림 3-10. Thread 들이 IO Wait 를할때시간에따른 Thread Dump 상황 > "ExecuteThread: '42' for queue: 'default'" daemon prio=5 tid=0x3504b0 nid=0x34 runnable [0x9607e000..0x9607fc68] at java.net.socketinputstream.socketread(native Method) at java.net.socketinputstream.read(socketinputstream.java:85) at oracle.net.ns.packet.receive(unknown Source) at oracle.net.ns.netinputstream.getnextpacket(unknown Source) at oracle.net.ns.netinputstream.read(unknown Source) at oracle.net.ns.netinputstream.read(unknown Source) at oracle.net.ns.netinputstream.read(unknown Source) at oracle.jdbc.ttc7.marengine.unmarshalub1(marengine.java:730) at oracle.jdbc.ttc7.marengine.unmarshalsb1(marengine.java:702) at oracle.jdbc.ttc7.oall7.receive(oall7.java:373) at oracle.jdbc.ttc7.ttc7protocol.dooall7(ttc7protocol.java:1427) at oracle.jdbc.ttc7.ttc7protocol.fetch(ttc7protocol.java:911) at oracle.jdbc.driver.oraclestatement.doexecutequery(oraclestatement.java:1948) at oracle.jdbc.driver.oraclestatement.doexecutewithtimeout(oraclestatement.java:2137) at oracle.jdbc.driver.oraclepreparedstatement.executeupdate(oraclepreparedstatement.java:404) at oracle.jdbc.driver.oraclepreparedstatement.executequery(oraclepreparedstatement.java:344) at weblogic.jdbc.pool.preparedstatement.executequery(preparedstatement.java:51) at weblogic.jdbc.rmi.internal.preparedstatementimpl.executequery(preparedstatementimpl.java:56) at weblogic.jdbc.rmi.serialpreparedstatement.executequery(serialpreparedstatement.java:42) at com.xxxx 생략 at..
< 그림 3-11. Thread 가 DB Query Wait 에걸려있는 stack > 그림 3-10 상황은 DB 에 Query 를보내고 response 를 Thread 들이기다리고있는상황이다. AP 에서 JDBC 를통해서 Query 를보냈는데, Response 가오지않으면계속기다리게있게되고, 다른 Thread 가같은 DB 로보냈는데. Response 가오지않고, 이런것들이중복되서결국은사용할수없는 Thread 가없게되서새로운 request 를처리하지못하게되고, 기존에 Query 를보낸내용도응답을받지못하는상황이다. < 그림 3-11> 은각각의 Thread 의 stack dump 인데, 그내용을보면 ExecuteQuery 를수행한후에, Oracle 로부터데이타를 read 하기위해대기하는것을확인할수있다. 이런현상은주로 DB 사용시대용량 Query( 시간이많이걸리는 ) 를보냈거나, DB 에 lock 들에의해서발생하는경우가많으며, 그외에도 Socket 이나 File 을사용하는 AP 의경우 IO 문제로인해서발생하는경우가많다. 해결방안이문제의해결방안은 Thread Dump 에서문제가되는부분을발견한후에, 해당소스코드나시스템등에접근하여문제를해결해야한다. - CASE 4. high CPU usage 시스템이느려지거나거의멈추었을때, 우리가초기에해볼수있는조치가무엇인가하면, 현재시스템의 CPU 사용률을체크해볼필요가있다. 해당시스템의 CPU 사용률이높은경우, 문제가되는경우가종종있는데. 이런문제에서는 CPU 를많이사용하는모듈을찾아내는것이관건이다. 이를위해서먼저 top 이나 glance(hp UX) 를통해서 CPU 를많이점유하고있는 Process 를판독한다. 만약 WAS 이외의다른 process 가 CPU 를많이사용하고있다면, 그 Process 의 CPU 과사용원인을해결해야한다. CPU 사용률이외에도, Disk IO 양이많지는않은지.. WAS 의 JVM Process 가 Swap out (DISK 로 SWAP 되는현상 ) 이없는지살펴보도록한다. JVM Process 가 Swapping 이되면실행속도가엄청나게느려지기때문에, JVM Process 는 Swap out 되어버리면안된다.
일단 CPU 를과사용하는원인이 WAS Process 임을발견했으면프로그램상에어떤 Logic 이 CPU 를과점유하는지를찾아내야한다. 방식을정리해보면다음과같다. WAS Process 의 Thread 별 CPU 사용률을체크한다. 1) Thread Dump 를추출한다. 2) 1) 과, 2) 에서추출한정보를 Mapping 하여, 2) 의 Thread Dump 상에서 CPU 를과점유하는 Thread 를찾아내어해당 Thread 의 Stack 을분석하여 CPU 과점유하는원인을찾아낸다. 대부분이런요인을분석해보면다음과같은원인이많다. - 과도한 String 연산으로인해서 CPU 사용률이높아지는경우잘알고있다시피 String 연산은 CPU 를아주많이사용한다. String 과 Loop 문 (for,while 등 ) 의사용은 CPU 부하를유발하는경우가많기때문에가급적이면 String Buffer 를사용하도록하자. - 과도한 RMI Cal RMI 호출은 Java Object 를 Serialize 하고 Deserialize 하는과정을수반하는데, 이는 CPU 를많이사용하는작업이기때문에사용에주의를요한다. 특별히 RMI 를따로코딩하지않더라도 EJB 를호출하는것이 Remote Call 일때는기본적으로 RMI 호출을사용하게되고, 부하량이많을때이부분이주로병목의원인이되곤한다. 특히 JSP/Servlet EJB 호출되는것이같은 System 의같은 JVM Process 안이라도 WAS 별로별도의설정을해주지않으면 RMI Call 을이용하는형태로구성이되기때문에, 이에대한배려가필요하다. WebLogic 에서 Call By Reference 를위한호출방법정의 참고로 WebLogic 의경우에는 Servlet/JSP EJB 를호출하는방식을 Local Call 을이용하기위해서는같은 ear 파일내에패키징해야하고, EJB 의 weblogicejb-jar.xml 에 enable-call-by-reference 를 true 로설정해줘야한다. (8.1 이상 ) 자세한내용은 http://www.j2eestudy.co.kr/lecture/ lecture_read.jsp?table=j2ee&db=lecture0201_1&id=1&searchby=subject&searchkey=deploy&block=0&page=0 를참고하시바란다. - JNDI lookup
JNDI lookup 은 Server 의 JNDI 에 Binding 된 Object 를읽어오는과정이다. 이과정은위에서설명한 RMI call 로수행이되는데. 특히 EJB 를호출하기위해서 Home 과 Remote Interface 를 lookup 하는과장에서종종 CPU 를과점유하는형태를관찰할수있다. 그래서 JNDI lookup 의경우에는 EJB Home Interface 를 Caller Side(JSP/Servlet 또는 poor Java client) 등에서 Caching 해놓고사용하는것을권장한다. 단. 이경우에는 EJB 의 redeploy 기능을제약받을수있다. 다음은각 OS 별로 CPU 사용률이높은 Thread 를검출해내는방법이다. Solaris 에서 CPU 사용률이높은 Thread 를검출하는방법 1.prstat 명령을이용해서 java process 의 LWP(Light Weight process) CPU 사용률을구한다. % prstat L p [WeblogicPID] 1 1 PID USERNAME SIZE RSS STATE PRI NICE TIME CPU PROCESS/LWPID 26148 bwcho 143M 58M sleep 58 0 0:00.00 0.0% java/11 26148 bwcho 143M 58M sleep 58 0 0:00.00 0.0% java/10 26148 bwcho 143M 58M sleep 58 0 0:00.00 0.0% java/9 26148 bwcho 143M 58M sleep 58 0 0:00.00 0.0% java/8 26148 bwcho 143M 58M sleep 58 0 0:00.00 0.0% java/7 26148 bwcho 143M 58M sleep 59 0 0:00.00 0.0% java/6 26148 bwcho 143M 58M sleep 59 0 0:00.00 0.0% java/5 26148 bwcho 143M 58M sleep 59 0 0:00.00 0.0% java/4 26148 bwcho 143M 58M sleep 58 0 0:00.08 0.0% java/3 26148 bwcho 143M 58M sleep 58 0 0:00.00 0.0% java/2 26148 bwcho 143M 58M sleep 58 0 0:00.14 0.0% java/1 2. pstack 명령어를이용해서 native thread 와 LWP 간의 id mapping 을알아낸다. ( 전에먼저 java process 가 lwp 로돌아야되는데, startweblogic.sh 에 LD_LIBRARY_PATH 에 /usr/lib/lwp 가포함되어야한다.) % pstack [WebLogicPID] ----------------- lwp# 8 / thread# 24 -------------------- ff29b3dc lwp_sema_wait (f2481e30) ff359818 _park (f2481e30, ff37e000, 0, f2481d78, 25020, f1c81d78) + 114 ff3594f0 _swtch (f2481d78, 0, ff37e000, 5, 1000, e) + 424 ff358004 cond_wait (4356, 36dfb0, ff37e000, 36dfc8, f2481d78, 0) + e4 fe514f44 1cNObjectMonitorEwait6MxlpnGThread v_ (fe76fb0c, 36dfc8, 36dfb0, fe
3. 1 에서얻은 LWP ID 를 pstack log 를통해서분석해보면어느 Thread 에 mapping 되는지를확인할수있다. 여기서는 LWP 8 이 Thread 24 과 mapping 이되고있음을볼수있다. kill 3 [WebLogicPID] 를해서 ThreadDump 를얻어낸다. "ExecuteThread: '11' for queue: 'default'" daemon prio=5 tid=0x36d630 nid=0x18 w aiting on monitor [0xf2481000..0xf24819e0] at java.lang.object.wait(native Method) at java.lang.object.wait(object.java:415) at weblogic.kernel.executethread.waitforrequest(executethread.java:114) at weblogic.kernel.executethread.run(executethread.java:138) : Thread dump 에서 nid 라는것이있는데, 2 에서얻어낸 thread id 를 16 진수로바꾸면이값이 nid 값과같다. 즉 2 에서얻어낸 thread 24 는 16 진수로 0x18 이기때문에, thread dump 에서 nid 가 0x18 인쓰레드를찾아서어떤작업을하고있는지를찾아내면된다. AIX Unix 에서 CPU 사용률이높은 Thread 검출해내기 1. ps 명령을이용하여, WebLogic Process 의각시스템 thread 의사용률을구한다. % ps mp [WeblogicPID] 0 THREAD USER PID PPID TID ST CP PRI SC WCHAN F TT BND COMMAND usera 250076 217266 - A 38 60 72 * 242011 pts/0 - /wwsl/sharedinstalls/aix/jdk130/... - - - 315593 Z 0 97 1 - c00007 - - - - - - 344305 S 0 60 1 f1000089c020e200 400400 - - - - - - 499769 S 0 60 1 f1000089c0213a00 400400 - - - - - - 540699 S 0 60 1 f100008790008440 8410400 - - - - - - 544789 S 0 60 1 f100008790008540 8410400 - - - - - - 548883 S 0 60 1 f100008790008640 8410400 - - - - - - 552979 S 0 60 1 f100008790008740 8410400 - - - - - - 565283 Z 0 60 1 - c00007 - - - - - - 585783 S 0 60 1 f100008790008f40 8410400 - - - - - - 589865 Z 0 80 1 - c00007 - - - - - - 593959 S 1 60 1 f100008790009140 8410400 - - - - - - 610365 S 0 60 1 f100008790009540 8410400 - - - - - - 614453 S 0 60 1 f100008790009640 8410400 - - - - - - 618547 S 0 60 1 f100008790009740 8410400 - - - - - - 622645 S 0 60 1 f100008790009840 8410400 - - - - - - 626743 S 0 60 1 f100008790009940 8410400 - - - - - - 630841 S 0 60 1 f100008790009a40 8410400 - - - - - - 634941 S 0 60 1 f100008790009b40 8410400 - - - - - - 639037 S 0 60 1 f100008790009c40 8410400 - - -
- - - 643135 S 0 60 1 f100008790009d40 8410400 - - - - - - 647233 S 0 60 1 f100008790009e40 8410400 - - - - - - 651331 S 0 60 1 f100008790009f40 8410400 - - - - - - 655429 S 0 60 1 f10000879000a040 8410400 - - - - - - 659527 S 0 60 1 f10000879000a140 8410400 - - - - - - 663625 S 0 60 1 f10000879000a240 8410400 - - - - - - 667723 S 37 78 1 f1000089c020f150 400400 - - - - - - 671821 S 0 60 1 f10000879000a440 8410400 - - 여기서 CP 가가장높은부분을찾는다. 이시스템쓰레드가 CPU 를가장많이점유하고있는시스템쓰레드이다. ( 여기서는 66723 이다.) 2. dbx 명령을이용해서 1. 에서찾은시스템쓰레드의 Java Thread ID 를얻어온다. 1) % dbx a [WebLogicPID] 2) dbx 에서 thread 명령을치면 Thread ID 를 Listing 할수있다. thread state-k wchan state-u k-tid mode held scope function... $t15 wait 0xf10000879000a140 blocked 659527 k no sys _event_sleep $t16 wait 0xf10000879000a240 blocked 663625 k no sys _event_sleep $t17 run running 667723 k no sys JVM_Send $t18 wait 0xf10000879000a440 blocked 671821 k no sys _event_sleep $t19 wait running 675919 k no sys poll $t20 wait 0xf10000879000a640 blocked 680017 k no sys _event_sleep k-tid 항목에서 1 에서찾은 Thread ID 를찾고, 그 k-tid 에해당하는 thread id 를찾는다. ( 여기서는 $t17 이된다.) 3) dbx 에서 $t17 번쓰레드의 Java Thread ID 를얻는다. dbx 에서 th info 17 이라고치면 $t17 번쓰레드의정보를얻어온다. (dbx) th info 17 thread state-k wchan state-u k-tid mode held scope function $t17 run running 667723 k no sys JVM_Send general: pthread addr = 0x3ea55c68 size = 0x244 vp addr = 0x3e69e5e0 size = 0x2a8 thread errno = 2 start pc = 0x300408b0 joinable = no pthread_t = 1011 scheduler: kernel = user = 1 (other) event : event = 0x0 cancel = enabled, deferred, not pending stack storage: base = 0x3ea15000 size = 0x40000 limit = 0x3ea55c68 sp = 0x3ea55054 pthread_t 항목에서 Java Thread ID 를얻는다. 여기서는 1011 이된다.
3. Java Thread Dump 에서 2 에서얻어온 Java Thread ID 를이용해서해당 Java Thread 를찾아서 Java Stack 을보고 CPU 를많이사용하는원인을찾아낸다. 1) kill 3 [WebLogicPID] 2) Thread dump 를보면 native ID 라는항목이있는데, 2. 에서찾은 Java Thread ID 와이항목이일치하는 Execute Thread 를찾으면된다. "ExecuteThread: '11' for queue: 'default'" (TID:0x31cf86d8, sys_thread_t:0x3e5ea108, state:r, native ID:0x1011) prio=5 at java.net.socketoutputstream.socketwrite(native Method) at java.net.socketoutputstream.write(socketoutputstream.java(compiled Code)) at weblogic.servlet.internal.chunkutils.writechunktransfer(chunkutils.java(compiled Code)) at weblogic.servlet.internal.chunkutils.writechunks(chunkutils.java(compiled Code)) at weblogic.servlet.internal.chunkoutput.flush(chunkoutput.java(compiled Code)) at weblogic.servlet.internal.chunkoutput.checkforflush(chunkoutput.java(compiled Code)) at weblogic.servlet.internal.chunkoutput.write(chunkoutput.java(compiled Code)) at weblogic.servlet.internal.chunkoutput.write(chunkoutput.java(compiled Code)) at weblogic.servlet.internal.chunkoutputwrapper.write(chunkoutputwrapper.java(compiled Code)) at weblogic.servlet.internal.chunkwriter.write(chunkwriter.java(compiled Code)) at java.io.writer.write(writer.java(compiled Code)) at java.io.printwriter.write(printwriter.java(compiled Code)) at java.io.printwriter.write(printwriter.java(compiled Code)) at java.io.printwriter.print(printwriter.java(compiled Code)) at java.io.printwriter.println(printwriter.java(compiled Code)) at examples.servlets.helloworldservlet.service(helloworldservlet.java(compiled Code)) at javax.servlet.http.httpservlet.service(httpservlet.java:853) at weblogic.servlet.internal.servletstubimpl$servletinvocationaction.run(servletstubimpl.java: 1058) at weblogic.servlet.internal.servletstubimpl.invokeservlet(servletstubimpl.java:401) at weblogic.servlet.internal.servletstubimpl.invokeservlet(servletstubimpl.java:306) at weblogic.servlet.internal.webappservletcontext$servletinvocationaction.run(webappservle tcontext.java:5445) at weblogic.security.service.securityservicemanager.runas(securityservicemanager.java(comp iled Code)) at weblogic.servlet.internal.webappservletcontext.invokeservlet(webappservletcontext.java: 3105) at weblogic.servlet.internal.servletrequestimpl.execute(servletrequestimpl.java:2588) at weblogic.kernel.executethread.execute(executethread.java(compiled Code)) at weblogic.kernel.executethread.run(executethread.java:189) HP Unix 에서 CPU 사용률이높은 Thread 검출해내기 1 먼저 JVM 이 Hotspot mode 로작동하고있어야한다. (classic 모드가아니어야한다.) 옵션을주지않았으면 Hotspot 모드가 default 이다.
2. glance 를실행해서 G 를누르고 WAS 의 PID 를입력한다. 각 Thread 의 CPU 사용률이실시간으로모니터링이되는데. 여기서 CPU 사용률이높은 Thread 의 TID 를구한다. 3. kill 3 을이용해서 Thread dump 를추출해서. 2 에서구한 TID 와 Thread Dump 상의 lwp_id 가일치하는 Thread 를찾으면된다. 지금까지 Thread Dump 를이용하는방법을간단하게살펴보았다. 이방법을이용하면 WAS 와그위에서작동하는 Appllication 의 Slow down 이나 hangup 의원인을대부분분석해낼수있으나, Thread Dump 는어디까지나분석을위한단순한정보이다 Thread Dump 의내용이 Slow down 이나 hang up 의원인이될수도있으나, 반대로다른원인이존재하여그결과로 Thread Dump 와같은 Stack 이나올수있기때문에, 여러원인을동시에살펴보면서분석할수있는능력이필요하다. 3. Slow down in JVM WAS 의성능에큰영향을주는것중의하나가 JVM 이다.
JVM 의튜닝여부에따라서 WAS 상에서작동하는 Ap 의성능을크게는 20~30% 까지향상시킬수있는데, 우리가지금살펴보고있는 slow down 과 hangup 을일으키는직접적인요인이되는것은 JVM 의 Full GC 이다. 간단하게 JVM 의메모리구조를검토하고넘어가보도록하자. < 그림 4-1. JVM 의메모리구조 > JVM 은크게 New 영역과 Old 영역, 그리고 Perm 영역 3 가지로분류가된다. Perm 영역은 Class 나 Method 들이로딩되는영역이고성능상의영향을거의미치지않는다. 우리가주목해야할부분은객체의생성과저장에관련되는 New 와 Old 영역인데, 모든객체는생성이되자마자 New 영역에저장되고, 시간이지남에따라이객체들은 Old 영역으로이동이된다. New 영역을 Clear 하는과정을 Minor GC 라하고, Old 영역을 Clear 하는과정은 Major GC 또는 Full GC 라하는데, 성능상의문제는이 Full 영역에서발생한다. Minor GC 의경우는 1 초이내에아주고속으로이뤄지는작업이기때문에, 신경을쓸필요가없지만, Full GC 의경우에는시간이매우오래걸린다. 또한 Full GC 가발생할동안은 Application 이순간적으로멈춰버리기때문에시스템이순간적으로 Hangup 으로보이거나또는 Full GC 가끝나면서갑자기 request 가몰려버리는현상때문에종종 System 의장애를발생시키는경우가있다. Full GC 는통상 1 회에 3~5 초정도가적절하고, 보통하루에 JVM Instance 당 5 회이내가적절하다고여겨진다. ( 절대값은없다.)
Full GC 가자주일어나는것이문제가될경우에는 JVM 의 Heap 영역을늘려주면천천히일어나지만반대로 Full GC 에소요되는시간이증가한다. 개당 Full GC 시간이오래걸릴경우에는 JVM 의 Heap 영역을줄여주면빨리 Full GC 가끝나지만반대로 Full GC 가자주일어난다는단점이있다. 그래서이부분에대한적절한 Tuning 이필요하다. 대부분의 Full GC 로인한문제는 JVM 자체나 WAS 의문제이기보다는그위에서구성된 Application 이잘못구성되어메모리를과도하게사용하거나오래점유하는경우가있다. 예를들어대용량 DBMS Query 의결과를 WAS 상의메모리에보관하거나, 또는 Session 에대량의데이타를넣는것들이대표적인예가될수가있다. 좀더자세한튜닝방법에대해서는 http://www.j2eestudy.co.kr/lecture/lecture_read.jsp?db=lect ure0401_1&table=j2ee&id=1 를참고하기바란다. 4. Slow down analysis in DBMS Application 이느려지는원인중의많은부분을차지하고있는것은 DBMS 의성능문제가있는경우가많다. 흔히들 DBMS Tuning 을받았더니성능이많이향상되었다고하는경우가많은데, 그건그만큼 DB 설계를제대로하지못했다는이야기가된다. DBMS 자체 Tuning 에대한것은이문서와는논외기때문에제외하기로하고, DBMS 에전송되는각각의 SQL 문장의실행시간을 Trace 할수있는것만으로도많은성능향상을기대할수있는데, 간단하게 SQL 문장을실행시간은아래방법들을이용해서 Trace 할수있다. http://eclipse.new21.org/phpbb2/viewtopic.php?printertopic=1&t= 380&start=0&postdays=0&postorder=asc&vote=viewresult http://www.j2eestudy.co.kr/qna/bbs_read.jsp?table=j2ee&db=qna 0104&id=5&searchBy=subject&searchKey=sql&block=0&page=0 5. Slow down analysis in Webserver & network
WAS 와 DBMS 앞단에는 WebServer 와 Network 이있기때문에이 Layer 에서문제가되면속도저하를가지고올수있다. 필자의경험상대부분의 slow down 이나 hangup 은이부분에서는거의일어나지않지만성능상에종종영향을주는 Factor 가있는데, WebServer 와 Client 간의 KeepAlive 특히 WebServer 의 Keep Alive 설정이그것이다. WebBrowser 와 WebServer 간에는 KeepAlive 설정을하는것이좋은데. 그이유는 WebBrowser 에서하나의 HTML 페이지를로딩하기위해서는 Image 와 CSS 등의여러파일등을로딩하는데, KeepAlive 설정이없으면각각의파일을로딩하는것에각각의 Connection 을 open,request,download,close 를한다. 잘들알고있겠지만 Socket 을 open 하고 close 하는데에는많은자원이소요된다. 그래서한번연결해놓은 Connection 을계속이용해서 HTTP data 를주고받는설정이 KeepAlive 이다. 이 KeepAlive 설정은웹을이용한서비스제공에서많은성능변화를주기때문에특별한이유가없는한 KeepAlive 설정을유지하기바란다. 설정방법은각 WebServer 의메뉴얼을참고하기바란다. Apache2.0 의 Keep Alive 설정은 http://httpd.apache.org/docs-2.0/mod/core.html#keepalive 를참고하기바란다. Default 가 KeepAlive 가 On 으로되어있다. WebServer 와 WAS 간의 KeepAlive WebServer 와 WAS 간에는 WebServer 에서받은 request 를 forward 하기위해서 WebServer Side 에 WAS 와통신을하기위한 plug-in 이라는모듈을설치하게된다. 이역시 WebServer 와 Client 간의통신과의같은원리로 KeepAlive 를설정하게되는데, 이역시성능에영향을줄수있는부분이기때문에가급적이면설정하기를권장한다. WebLogic 에서 Webserver 와의 KeepAlive 설정은 http://edocs.bea.com/wls/docs81/plugins/plugin_params.html#1143055 을참고하기바란다. Default 는 KeepAlive 가 True 로설정되어있다
OS 에서 Kernel Parameter 설정 OS 의 TCP/IP Parameter 와, Thread 와 Process 등의 Kernel Parameter 설정이운영에있어서영향을미치는경우가있다. 이 Parameter 들은 Tuning 하기가쉽지않기때문에, WAS 또는 OS Vendor 에서제공하는문서를통해서 Tuning 하기바란다. WebLogic 의 OS 별설정정보은 http://edocs.bea.com/platform/suppconfigs/configs81/81_over/overview.html 를참고하기바란다. 6. Common mistake in developing J2EE Application 지금까지간단하게나마 J2EE Application 의병목구간을분석하는부분에대해서알아보았다. 대부분의병목은 Application 에서발생하는경우가많은데, 이런병목을유발하는 Application 에자주발생하는개발상의실수를정리해보도록하자. 1) Java Programming - sycnronized block 위에서도설명했듯이 sychronized 메소드는 lock contention 과 deadlock 등의문제를유발할수있다. 꼭필요한경우에는사용을해야하지만, 이점을고려해서 Coding 해야한다. - String 연산이미많은개발자들이알고있는내용이겠지만 String 연산특히 String 연산중 + 연산은 CPU 를매우많이소모하게되고종종 slow down 의직접적인원인이되는경우가매우많다. String 보다는가급적 StringBuffer 를사용하기바란다. - Socket & file handling Socket 이나 File Handling 은 FD (File Descriptor) 를사용하게되는데, 이는유한적인자원이기때문에사용후에반드시 close 명령을이용해서반환해야한다. 종종 close 를하지않아서, FD 가모자르게되는경우가많다. 2) Servlet/JSP Programming
- JSP Buffer Size Jsp 에서는 JSP 의출력내용을저장하는 buffer 사이즈를지정할수있다. <% page buffer= 12kb %> 이 buffer size 는출력내용을 buffering 했다가출력하는데, 만약에쓰고자하는내용이 Buffer size 보다클경우에는여러번에걸쳐서 socket write 가일어나기때문에 performance 에영향을줄수있으므로가능하다면 buffersize 를화면에뿌리는내용의크기를예측해서지정해주는것이바람직하다. 반대로너무큰버퍼를지정해버리면메모리가불필요하게낭비될수있기때문에이점을주의하기바란다. 참고로 jsp page buffer size 는지정해주지않는경우 default 로 8K 로지정된다. - member variable Servlet/JSP 는기본적으로 Multi Thread 로동작하기때문에, Servlet 과 JSP 내에서선언된멤버변수들은각 Thread 간에공유가된다. 그래서이변수들을 read/write 할경우에는 sychronized method 로구성해야하는데, 이 synchronized 는속도저하를유발할수있기때문에, member 변수로는 read 만하는객체를사용하는게좋다. 특히 Servlet 이나 JSP 에서 Data Base Connection 을멤버변수로선언하여 Thread 간공유하는예가있는데, 이는별로좋지않은프로그래밍방법이고, 이런형태의패턴은 Servlet 이단하나만실행되거나하는것과같은제약된조건아래에서만사용해야한다. - Out of Memory in file upload JSP 에서 File upload control 을사용하는경우가많다. 이 control 을구현하는과정에서 upload 되는파일내용을몽땅메모리에저장했다가업로드가끝나면한꺼번에 file 에 writing 하는경우가있는데, 이는큰사이즈의파일을업로드할때, 파일사이즈만큼의메모리용량을요구하기때문에, 자칫하면 Out Of Memory 에러를발생시킬수있다. File upload 는 buffer 를만들어서읽고, 파일에쓰는작업을병행하도록해야한다. 3) JDBC Programming - Connection Leak JDBC Programming 에서가장대표적으로발생되는문제가 Connection Leak 이다. Database Connection 을사용한후에 close 않아서생기는문제인데,Exception 이발생하였을때도반드시 Connection 을 close 하도록해줘야한다.
conn = getconnection(); try{ do something }catch(exception e){ dosomething(); } }finally{ if(stmt!=null) stmt.close(); if(conn!=null) conn.close(); } < 그림. Connection close 의올바른예 > - Out of memory in Big size query result SQL 문장을 Query 하고나오는 resultset 을사용할때, 모든 resultset 의결과를 Vector 나 hashtable 등을이용해서메모리에저장해놓는경우가있다. 이런경우에는평소에는문제가없지만, SQL Query 의결과가 10 만건이넘는것과같은대용량일때이모든데이타를메모리상에저장할려면 Out Of Memory 가나온다. Query 의결과값을처리할때는 ResultSet 을직접리턴받아서사용하는것이메모리활용면에서좀더바람직하다. - Close stmt & resultset JDBC 에서 Resultset 이나 Statement 객체는기본적으로 Connection 을 close 하게되면자동으로닫히게된다. 그러나 WAS 나 Servlet Container 의경우에는성능향상을위해서 Connection Pooling 기법을이용해서 Connection 을관리하기때문에 Connection Pooling 에서 Connection 을 close 하는것은실제로 close 하는것이아니라 Pool 에반환하는과정이기때문에해당 Connection 에연계되어사용되고있는 Statement 나 ResultSet 이닫히지않는다. Connection Pooling 에서 Statement 와 ResultSet 을사용후에닫아주지않으면 Oracle 에서 too many open cursor 와같은에러가나오게된다. (Statement 는 DB 의 Cursor 와 mapping 이된다.) 4) EJB Programming - When we use EJB? EJB 는분명강력하고유용한개발기술임에는틀림이없다. 그러나 EJB 의장점과용도를모르고사용하면오히려안쓰는것만못한경우가많다. 각 EJB 모델 (Session Bean,Entity Bean) 이어떤때유용한지를알고사용하고, 정확한 Transaction Model 등을결정해서사용해야한다. - Reduce JNDI look up
위에서도설명했듯이 EJB 의 Home Interface 를 lookup 해오는과정은객체의 Serialization/DeSerialization 을동반하기때문에, 시스템성능에영향을줄수있다. EJB Home 을한번 look up 한후에는 Hashtable 등에저장해서반복해서 Remote Call(Serialization / DeSerialization) 하는것을줄이는게좋다. - Do not use hot deploy in production mode WAS Vendor 마다 WAS 운영중에 EJB 를 Deploy 할수있는 HotDeploy 기능을제공한다. 그러나이는 J2ee spec 에있는구현이아니라각 vendor 마다개발의편의성을위해서제공하는기능이다. 운영중에 EJB 를내렸다올리는것은위험하다. (Transaction 이수행중이기때문에 ) Hot Deploy 기능은개발중에만사용하도록하자. 5) JVM Memory tuning - Basic Tuning Application 을개발해놓고, 운영환경으로 staging 할때별도의 JVM 튜닝을하지않는경우가많다. 튜닝이아니더라도최소한의메모리사이즈와 HotSpot VM 모델 (server/client) 는설정해줘야지어느정도의 Application 의성능을보장받을수있다. 최소한메모리사이즈와 VM 모델정도는설정을해주고운영을하도록하자. 6. 결론 J2EE Application 의병목구간을확인하기위해서는그문제를발견하고툴과경험을이용해서문제의원인을발견하고제거해야한다. 대부분의 WAS 또는 User Application 의 slow down 이나 hang up 은 Thread dump 를통한분석을통해서대부분발견및해결을할수있다. 그외에부분 JVM 이나 WebServer,Network 등에대해서는별도의경험과 Log 분석등을알아내야하고 DB 에의한 slow down 이나 hang up 현상은 DB 자체의분석이필요하다.