최근 Tomcat7 에서 다음과 같이 코드를 바꾸었다.
성능은 오히려 System.arraycopy()가 좋은데, Arrays.copyOf를 쓰는 걸까 하는 생각이 들었다.
(참고 : http://www.javapractices.com/topic/TopicAction.do?Id=3)


-        System.arraycopy(applicationEventListenersObjects, 0,
-                newListeners, 0, len);
+        Object[] newListeners = Arrays.copyOf(applicationEventListenersObjects,
+                len + 1);


왜 바꾸었나 API를 확인 해보니..
static
<T> T[]
copyOf(T[] original, int newLength) 
          Copies the specified array, truncating or padding with nulls (if necessary) so the copy has the specified length.
static
<T,U> T[]
copyOf(U[] original, int newLength, Class<? extends T[]> newType) 
          Copies the specified array, truncating or padding with nulls (if necessary) so the copy has the specified length


jdk5의 시절에는 아래와 같이 java generic를 이용하여 Array 에 대한 type별로 copy되는  array를 구하려는 메소드를 다음과 같이 만들었는데..

@SuppressWarnings("unchecked")
private final <T> T[] copy(T[] source) {
   Class type = source.getClass().getComponentType();
   T[] target = (T[])Array.newInstance(type, source.length);
   System.arraycopy(source, 0, target, 0, source.length);
   return target;
}


JDK6부터는 Arrays.copyOf가 위의 메소드를 대신 하게 되었다. 
톰캣 정신은 코드 유지보수성과 세련미가 우선이지. 민감한 성능차이는 아닌듯 싶다..


Posted by '김용환'
,
Posted by '김용환'
,


"ClassLoader deadlock when compiling JSP pages in 6.0.26 " 이라는 버그 질라가 떳다..

https://issues.apache.org/bugzilla/show_bug.cgi?id=48903

6.0.26 버젼의 WEB Class Loader 클래스에 약간의 문제가 있다..
아직 문제는 없지만, 문제가 일어날 소지가 있으므로.. 반드시 내용을 적어둔..


위 링크를 보면, Deadlock를 재현하여 Thread dump 를 떠 놓은 것이 있다. 서로가 Deadlock에 빠져있다.
헐..

Found one Java-level deadlock:
=============================
"Handling GET /job/Project1Trunk/ : http-8080-40":
  waiting to lock monitor 0x000000005b013e50 (object 0x00002aaac2cc9b58, a java.net.URLClassLoader),
  which is held by "Handling GET /job/Project2Trunk/disk-usage/graph : http-8080-22"

"Handling GET /job/Project2Trunk/disk-usage/graph : http-8080-22":
  waiting to lock monitor 0x000000005b986958 (object 0x00002aaab4465798, a java.lang.String),
  which is held by "Handling GET /job/Project2Trunk/emma/graph : http-8080-14"

"Handling GET /job/Project2Trunk/emma/graph : http-8080-14":
  waiting to lock monitor 0x000000005b0169a0 (object 0x00002aaac266fc30, a org.apache.catalina.loader.WebappClassLoader),
  which is held by "Handling GET /job/Project2Trunk/test/trend : http-8080-15"

"Handling GET /job/Project2Trunk/test/trend : http-8080-15":
  waiting to lock monitor 0x000000005b986958 (object 0x00002aaab4465798, a java.lang.String),
  which is held by "Handling GET /job/Project2Trunk/emma/graph : http-8080-14"


(1)Thread
"Handling GET /job/Project1Trunk/ : http-8080-40" daemon prio=10 tid=0x000000005c3e9000 nid=0x2a8f waiting for monitor entry [0x0000000045228000]
   java.lang.Thread.State: BLOCKED (on object monitor)
 at sun.misc.Unsafe.defineClass(Native Method)

 at sun.reflect.ClassDefiner.defineClass(ClassDefiner.java:45)
 at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:381)
 at java.security.AccessController.doPrivileged(Native Method)
 at sun.reflect.MethodAccessorGenerator.generate(MethodAccessorGenerator.java:377)
 at sun.reflect.MethodAccessorGenerator.generateMethod(MethodAccessorGenerator.java:59)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:28)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)


(2)Thread
"Handling GET /job/Project2Trunk/disk-usage/graph : http-8080-22":
 at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1394)
 - waiting to lock <0x00002aaab4465798> (a java.lang.String)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:300)
 - locked <0x00002aaac2c3ed48> (a hudson.ClassicPluginStrategy$DependencyClassLoader)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:300)
 - locked <0x00002aaac2cc9b58> (a java.net.URLClassLoader)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:252)
 at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:320)
 - locked <0x00002aaac2cc9b58> (a java.net.URLClassLoader)

(3) Thread
"Handling GET /job/Project2Trunk/emma/graph : http-8080-14" daemon prio=10 tid=0x000000005c359000 nid=0x29d4 waiting for monitor entry [0x000000004370f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
 at java.lang.ClassLoader.checkCerts(ClassLoader.java:752)
 - waiting to lock <0x00002aaac266fc30> (a org.apache.catalina.loader.WebappClassLoader)

 at java.lang.ClassLoader.preDefineClass(ClassLoader.java:488)
 at java.lang.ClassLoader.defineClass(ClassLoader.java:615)


(4)Thread
"Handling GET /job/Project2Trunk/test/trend : http-8080-15" daemon prio=10 tid=0x000000005b093000 nid=0x29d5 waiting for monitor entry [0x0000000043810000]
   java.lang.Thread.State: BLOCKED (on object monitor)
 at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1394)

 - waiting to lock <0x00002aaab4465798> (a java.lang.String)
 at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1361)
 at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:320)
 - locked <0x00002aaac266fc30> (a org.apache.catalina.loader.WebappClassLoader)



이 부분을 어떻게 풀었는지 확인했다.

--- tomcat/tc6.0.x/trunk/java/org/apache/catalina/loader/ResourceEntry.java	2010/05/06 19:15:48	941867
+++ tomcat/tc6.0.x/trunk/java/org/apache/catalina/loader/ResourceEntry.java	2010/05/06 19:17:49	941868
@@ -47,7 +47,7 @@
     /**
      * Loaded class.
      */
-    public Class loadedClass = null;
+    public volatile Class loadedClass = null;
 
 
     /**

--- tomcat/tc6.0.x/trunk/java/org/apache/catalina/loader/WebappClassLoader.java	2010/05/06 19:15:48	941867
+++ tomcat/tc6.0.x/trunk/java/org/apache/catalina/loader/WebappClassLoader.java	2010/05/06 19:17:49	941868
@@ -1432,102 +1432,121 @@
      *
      * @exception ClassNotFoundException if the class was not found
      */
-    public Class loadClass(String name, boolean resolve)
+    public synchronized Class loadClass(String name, boolean resolve)
         throws ClassNotFoundException {
 
-        synchronized (name.intern()) {
-            if (log.isDebugEnabled())
-                log.debug("loadClass(" + name + ", " + resolve + ")");
-            Class clazz = null;
-    
-            // Log access to stopped classloader
-            if (!started) {
-                try {
-                    throw new IllegalStateException();
-                } catch (IllegalStateException e) {
-                    log.info(sm.getString("webappClassLoader.stopped", name), e);
-                }
+        if (log.isDebugEnabled())
+            log.debug("loadClass(" + name + ", " + resolve + ")");
+        Class clazz = null;
+
+        // Log access to stopped classloader
+        if (!started) {
+            try {
+                throw new IllegalStateException();
+            } catch (IllegalStateException e) {
+                log.info(sm.getString("webappClassLoader.stopped", name), e);
             }
-    
-            // (0) Check our previously loaded local class cache
-            clazz = findLoadedClass0(name);
+        }
+
+        // (0) Check our previously loaded local class cache
+        clazz = findLoadedClass0(name);
+        if (clazz != null) {
+            if (log.isDebugEnabled())
+                log.debug("  Returning class from cache");
+            if (resolve)
+                resolveClass(clazz);
+            return (clazz);
+        }
+
+        // (0.1) Check our previously loaded class cache
+        clazz = findLoadedClass(name);
+        if (clazz != null) {
+            if (log.isDebugEnabled())
+                log.debug("  Returning class from cache");
+            if (resolve)
+                resolveClass(clazz);
+            return (clazz);
+        }
+
+        // (0.2) Try loading the class with the system class loader, to prevent
+        //       the webapp from overriding J2SE classes
+        try {
+            clazz = system.loadClass(name);
             if (clazz != null) {
-                if (log.isDebugEnabled())
-                    log.debug("  Returning class from cache");
                 if (resolve)
                     resolveClass(clazz);
                 return (clazz);
             }
-    
-            // (0.1) Check our previously loaded class cache
-            clazz = findLoadedClass(name);
-            if (clazz != null) {
-                if (log.isDebugEnabled())
-                    log.debug("  Returning class from cache");
-                if (resolve)
-                    resolveClass(clazz);
-                return (clazz);
+        } catch (ClassNotFoundException e) {
+            // Ignore
+        }
+
+        // (0.5) Permission to access this class when using a SecurityManager
+        if (securityManager != null) {
+            int i = name.lastIndexOf('.');
+            if (i >= 0) {
+                try {
+                    securityManager.checkPackageAccess(name.substring(0,i));
+                } catch (SecurityException se) {
+                    String error = "Security Violation, attempt to use " +
+                        "Restricted Class: " + name;
+                    log.info(error, se);
+                    throw new ClassNotFoundException(error, se);
+                }
             }
-    
-            // (0.2) Try loading the class with the system class loader, to prevent
-            //       the webapp from overriding J2SE classes
+        }
+
+        boolean delegateLoad = delegate || filter(name);
+
+        // (1) Delegate to our parent if requested
+        if (delegateLoad) {
+            if (log.isDebugEnabled())
+                log.debug("  Delegating to parent classloader1 " + parent);
+            ClassLoader loader = parent;
+            if (loader == null)
+                loader = system;
             try {
-                clazz = system.loadClass(name);
+                clazz = loader.loadClass(name);
                 if (clazz != null) {
+                    if (log.isDebugEnabled())
+                        log.debug("  Loading class from parent");
                     if (resolve)
                         resolveClass(clazz);
                     return (clazz);
                 }
             } catch (ClassNotFoundException e) {
-                // Ignore
-            }
-    
-            // (0.5) Permission to access this class when using a SecurityManager
-            if (securityManager != null) {
-                int i = name.lastIndexOf('.');
-                if (i >= 0) {
-                    try {
-                        securityManager.checkPackageAccess(name.substring(0,i));
-                    } catch (SecurityException se) {
-                        String error = "Security Violation, attempt to use " +
-                            "Restricted Class: " + name;
-                        log.info(error, se);
-                        throw new ClassNotFoundException(error, se);
-                    }
-                }
+                ;
             }
-    
-            boolean delegateLoad = delegate || filter(name);
-    
-            // (1) Delegate to our parent if requested
-            if (delegateLoad) {
+        }
+
+        // (2) Search local repositories
+        if (log.isDebugEnabled())
+            log.debug("  Searching local repositories");
+        try {
+            clazz = findClass(name);
+            if (clazz != null) {
                 if (log.isDebugEnabled())
-                    log.debug("  Delegating to parent classloader1 " + parent);
-                ClassLoader loader = parent;
-                if (loader == null)
-                    loader = system;
-                try {
-                    clazz = loader.loadClass(name);
-                    if (clazz != null) {
-                        if (log.isDebugEnabled())
-                            log.debug("  Loading class from parent");
-                        if (resolve)
-                            resolveClass(clazz);
-                        return (clazz);
-                    }
-                } catch (ClassNotFoundException e) {
-                    ;
-                }
+                    log.debug("  Loading class from local repository");
+                if (resolve)
+                    resolveClass(clazz);
+                return (clazz);
             }
-    
-            // (2) Search local repositories
+        } catch (ClassNotFoundException e) {
+            ;
+        }
+
+        // (3) Delegate to parent unconditionally
+        if (!delegateLoad) {
             if (log.isDebugEnabled())
-                log.debug("  Searching local repositories");
+                log.debug("  Delegating to parent classloader at end: " + parent);
+            ClassLoader loader = parent;
+            if (loader == null)
+                loader = system;
             try {
-                clazz = findClass(name);
+                clazz = loader.loadClass(name);
                 if (clazz != null) {
                     if (log.isDebugEnabled())
-                        log.debug("  Loading class from local repository");
+                        log.debug("  Loading class from parent");
                     if (resolve)
                         resolveClass(clazz);
                     return (clazz);
@@ -1535,30 +1554,10 @@
             } catch (ClassNotFoundException e) {
                 ;
             }
-    
-            // (3) Delegate to parent unconditionally
-            if (!delegateLoad) {
-                if (log.isDebugEnabled())
-                    log.debug("  Delegating to parent classloader at end: " + parent);
-                ClassLoader loader = parent;
-                if (loader == null)
-                    loader = system;
-                try {
-                    clazz = loader.loadClass(name);
-                    if (clazz != null) {
-                        if (log.isDebugEnabled())
-                            log.debug("  Loading class from parent");
-                        if (resolve)
-                            resolveClass(clazz);
-                        return (clazz);
-                    }
-                } catch (ClassNotFoundException e) {
-                    ;
-                }
-            }
-    
-            throw new ClassNotFoundException(name);
         }
+
+        throw new ClassNotFoundException(name);
+
     }
 
 
@@ -2544,7 +2543,7 @@
         if (clazz != null)
             return clazz;
 
-        synchronized (name.intern()) {
+        synchronized (this) {
             clazz = entry.loadedClass;
             if (clazz != null)
                 return clazz;


--- tomcat/tc6.0.x/trunk/java/org/apache/jasper/servlet/JasperLoader.java	2010/05/06 19:15:48	941867
+++ tomcat/tc6.0.x/trunk/java/org/apache/jasper/servlet/JasperLoader.java	2010/05/06 19:17:49	941868
@@ -91,7 +91,7 @@
      *                                     
      * @exception ClassNotFoundException if the class was not found
      */                                    
-    public Class loadClass(final String name, boolean resolve)
+    public synchronized Class loadClass(final String name, boolean resolve)
         throws ClassNotFoundException {
 
         Class clazz = null;                
@@ -169,4 +169,4 @@
     public final PermissionCollection getPermissions(CodeSource codeSource) {
         return permissionCollection;
     }
-}
\ No newline at end of file
+}




Synchronized를 잘 정리했고, volatile을 써서 명확하게 Thread로 인한 optimization이 안되도록 했다.
병렬적으로 클래스로딩이 될 때, 문제가 꼬일 수 있는 부분이 패치한 것으로 보인다.







Posted by '김용환'
,

병렬 쓰레드 프로그래밍(java thread, concurrent)을 할 때, 주의해야 할 사항 중 하나로 꼽히는 부분을 얘기 드리고자 합니다.

모든 java api에서 클래스가 Thread safety한지 Unsafety를 명시적으로 적은 것도 있고 안 쓴 것도 있기 때문에, 병렬 쓰레드 프로그래밍을 할 때는 단순히 thread concurrent 패키지의 api을 이해할 뿐 아니라.. 사용하는 클래스의 Thread Safety를 확인해야 합니다.

 

또한 Thread Safety는 성능을 놓칠 수 있기 때문에 클래스에 대해서 많이 알고 있어야 하고, 적재적소에 쓸 수 있는 능력을 가지고 있기도 해야 합니다..

(StringBuilder StringBuffer의 차이점을 생각하면 좋을 것 같습니다.)

 

(참고 . 무엇이 Thread Safe한지 아닌지를 확인하는 좋은 방법은 API를 보고 소스를 까면서 (source inspection) 공부해야 합니다. 특히 ThreadLocal, Collection, Map은 많이 봐줘야 한다고 생각드는 군요..)

 

예를 하나 들어보겠습니다.

아주 대표적인 SimpleDateFormat의 경우를 들도록 하겠습니다.

 

다행히 SimpleDateFormat의 경우는 동기화에 대한 얘기가 적혀 있습니다.

(http://java.sun.com/javase/6/docs/api/java/text/DateFormat.html)

Synchronization

Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.

 

 

import java.text.ParseException;

import java.text.SimpleDateFormat;

import java.util.Date;

 

public class SimpleDateFormatNotSafety {

           static SimpleDateFormat df = new SimpleDateFormat("dd-MMM-yyyy");

           static String testdata[] = { "01-Jan-1999", "14-Feb-2001", "31-Dec-2007" };

 

           public static void main(String[] args) {

                     Runnable r[] = new Runnable[testdata.length];

                     for (int i = 0; i < r.length; i++) {

                                final int i2 = i;

                                r[i] = new Runnable() {

                                          public void run() {

                                                     try {

                                                                for (int j = 0; j < 1000; j++) {

                                                                          String str = testdata[i2];

                                                                          String str2 = null;

                                                                          Date d = df.parse(str);

                                                                          str2 = df.format(d);

                                                                          if (!str.equals(str2)) {

                                                                                     throw new RuntimeException(

                                                                                     "date conversion failed after " + j

                                                                                     + " iterations. Expected "

                                                                                     + str + " but got " + str2);

                                                                          }

                                                                }

                                                     } catch (ParseException e) {

                                                                throw new RuntimeException("parse failed");

                                                     }

                                          }

                                };

                                new Thread(r[i]).start();

                     }

           }

}

 

결과는 어떻게 나올까요? Thread unsafety한 결과를 보여줍니다.

Exception in thread "Thread-0" java.lang.RuntimeException: parse failed

       at SimpleDateFormatNotSafety$1.run(SimpleDateFormatNotSafety.java:31)

       at java.lang.Thread.run(Unknown Source)

Exception in thread "Thread-1" java.lang.RuntimeException: parse failed

       at SimpleDateFormatNotSafety$1.run(SimpleDateFormatNotSafety.java:31)

       at java.lang.Thread.run(Unknown Source)

Exception in thread "Thread-2" java.lang.RuntimeException: parse failed

       at SimpleDateFormatNotSafety$1.run(SimpleDateFormatNotSafety.java:31)

       at java.lang.Thread.run(Unknown Source)

 

 

어떤 분은 이 부분에 대해서 버그 리포트도 올렸습니다.

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335

 

해결방법은 결론은 clone을 써서 리턴하거나, DateFormat를 호출하는 모든 부분에 대해서 Synchronize하라고 되어 있습니다. Thread Unsafety 코드의 특징입니다.

 

Thread Unsafety SimpleDateFormat 객체를 쓰고 싶어하는 사람을 위해서 Apache Commons-lang에서는 FastDateFormat 이라는 클래스를 추천하고 있습니다.

(http://www.jdocs.com/lang/2.1/org/apache/commons/lang/time/FastDateFormat.html)

 

어떤 사람은 ThreadLocal을 이용해서 본인만의 Thread-safety한 클래스를 만들어서 사용하기도 합니다. (http://li-ma.blogspot.com/2007/10/thread-safe-date-formatparser.html)

 

톰캣 7 개발자는 SimpleDateFormat을 계속 사용하기도 합니다. Clone을 이용하는 멋진 테크닉을 구사하여 속도를 향상시키는 법을 잘 알고 있는 것이지요.

private static final SimpleDateFormat FORMAT_PROTOTYPE = new

SimpleDateFormat("...", Locale.ROOT);

static {

  format.setTimeZone(GMT_ZONE);

}

 

protected SimpleDateFormat format = (SimpleDateFormat)FORMAT_PROTOTYPE.clone();

 

Thread-safety는 양날의 검과 같기 때문에 잘 사용해야 될 것입니다

Posted by '김용환'
,


http://onjava.com/pub/a/onjava/2004/09/29/smtp-logging.html

  log4j java.util.logging
Manager class org.apache.log4j.LogManager java.util.logging.LogManager
Logger objects org.apache.log4j.Logger java.util.logging.Logger
Named loggers Supported Supported
Logging levels

Levels are declared in the org.apache.log4j.Level class. log4j has five pre-defined levels:

  1. FATAL
  2. ERROR
  3. WARN
  4. INFO
  5. DEBUG

Levels are declared in the java.util.logging.Level class. There are seven pre-defined levels:

  1. SEVERE
  2. WARNING
  3. INFO
  4. CONFIG
  5. FINE
  6. FINER
  7. FINEST
Log event object org.apache.log4j.spi.LoggingEvent java.util.logging.LogRecord
Output destinations Appender classes. These classes implement the org.apache.log4j.Appender interface. Handler classes. These classes extend java.util.logging.Handler.
Output message filtering Filter classes. These classes extend org.apache.log4j.spi.Filter. Filter classes. These classes must implement the java.util.logging.Filter interface.
Output message formatting Layout classes extend org.apache.log4j.Layout. Formatter classes extend java.util.logging.Formatter.
Programmatic configuration Supported Supported
Property file configuration Supported Supported
XML configuration Supported. The XML configuration file must comply with log4j.dtd. Not supported
Email notification SMTPAppender class SMTPHandler class (third-party extension



추후 다양한 Appender를 만들기 위해서 정확히 이해할 필요 있음..
Posted by '김용환'
,


Java Desktop 환경에서만 나타난다고 하고, java server나 standalone 어플은 영향받지 않음.
즉, PC 환경에서만 발견될 수 있는 문제임

 Java 개발 및 실행환경인 JDK/JRE 6 Update 10 이후 버전의 자바 배포 도구 (Java Deployment
      Toolkit) 플러그인과 ActiveX 컨트롤에서 매개변수로 전달되는 입력값 검증 오류 및
      Update 18 이후 버전의 자바 플러그인의 오류로 인해 원격코드실행 취약점이 발생 [1, 2, 4]
   o 공격자는 특수하게 조작된 웹 페이지로 사용자를 유도하여, 해당 시스템에서 악의적인
      Java 파일(JAR)을 실행할 수 있음


(http://www.oracle.com/technology/deploy/security/alerts/alert-cve-2010-0886.html)
(http://www.krcert.net/secureNoticeView.do?seq=-1&num=409)


Supported Products Affected

Java SE

• JDK and JRE 6 for Windows, Solaris, and Linux
영향버젼 JDK 6 update 10  ~ 19
    

Java for Business

• JDK and JRE 6 for Windows, Solaris and Linux

=> 문제 해결

JDK 6 update 20을 을 설치 및 업데이트하면 됨




Posted by '김용환'
,

out of memory error Profiling 관련해서 좋은 자료 링크

 

  

HPROF: A Heap/CPU Profiling Tool in J2SE 5.0

http://java.sun.com/developer/technicalArticles/Programming/HPROF.html

 

Troubleshooting Memory Leaks

http://java.sun.com/javase/6/webnotes/trouble/TSG-VM/html/memleaks.html

 

Profiling 한글 정보

http://wiki.ex-em.com/index.php/HProf

 

jdk에서 나온 troubling shooting guide 문서

http://java.sun.com/javase/6/webnotes/trouble/TSG-VM/TSG-VM.pdf

Posted by '김용환'
,


 

톰캣 메모리릭 문제에 대해서 많이 좋아질 것으로 예상됩니다.

 

스프링소스의 개발자이며, 톰캣 Commitor Mark Thomas(subversion id : markt)에 의해 Memory Leak protection 기능이 새로 생기게 되었습니다. 원래는 톰캣 7부터 적용하려고 했는데, 톰캣 6.0.24 부터 적용되었다고 합니다. 6.0.25버전에는 Memory leak 체크 api도 지원하는 군요~ 덜덜
다음의 내용으로 Change log 6.0.25에 있습니다. ..

“Add new "Find leaks" command to the Manager application”

 

Mark는 이 OOME가 발생하는 원인을 톰캣이 아닌 Jvm, library라고 언급하였고, 그 예를 적었습니다.

(1)   Application, Library code 에 의해서 발생

A.     JDBC driver

B.      Logging framework

C.      ThreadLocal object를 저장하고 remove하지 않아서

D.     쓰레드를 시작시키고 멈추지 않아서

(2)   Java Api에 의해서도 발생

A.     Using the javax.imageio API (the Google Web Toolkit can trigger this)

B.      Using java.beans.Introspector.flushCaches() (Tomcat does this to prevent memory leaks caused by this caching)

C.      Using XML parsing (the root cause is unknown due to a bug in the JRE)

D.     Using RMI (somewhat ironically, causes a leak related to the garbage collector)

E.      Reading resources from JAR files

 

이런 문제가 나타나는 것의 이유 중의 하나는 Java Class Loader에서는 아직 object에 대해서 de-register 기능이 없습니다. 그래서 톰캣 web class loader는 이런 부분을 지원했습니다.

 

Mark는 이런 OOME 문제 해결을 위해서 다음을 하였습니다.

Org.apache.ctalina.loader.WebappClassLoader (http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoader.java?view=markup )클래스에 clearRefereneces() 메소드를 추가하여 톰캣에 의해서 load object들을 de-register하여 gc 되도록 하였습니다. PermGen 지우고 시작하는 같습니다.

 

또한 org.apache.catalina.core.JreMemoryLeakPreventionListener 클래스
(http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/JreMemoryLeakPreventionListener.java?view=markup )
추가하여 원인파악을 쉽게 있도록 api 제공하였습니다.

Mark java.util.logging 처럼 JRE에서 로딩되는 경우에 대해서는 Tomcat단에서 처리할 없으니, 다른 LogManager 쓰라고 권고하고 있습니다.

 

Static ThreadLocal의 경우도 해결이 힘들었는데. Tomcat 6.0.24는 아래와 같이 detect를 하고 webclassloader에서 ThreadLocalMap inspect함으로서 잘 테스트했다고 적혀 있네요.

Mar 17, 2010 10:23:13 PM org.apache.catalina.loader.WebappClassLoader clearThreadLocalMap

SEVERE: A web application created a ThreadLocal with key of type [java.lang.ThreadLocal] (value [java.lang.ThreadLocal@44676e3f]) and a value of type [test.leak.threadlocal.value.MyCounter] (value [test.leak.threadlocal.value.MyCounter@62770d2e]) but failed to remove it when the web application was stopped. To prevent a memory leak, the ThreadLocal has been forcibly removed.

 

Tomcat manager "Find leaks" 누르면 아래와 같이 알려줄 수는 있으나, 상세한 것은 Eclipse MAT (http://www.eclipse.org/mat/) 쓰라고 합니다.

The following web applications were stopped (reloaded, undeployed), but their
classes from previous runs are still loaded in memory, thus causing a memory
leak (use a profiler to confirm):
/testWeb

 

 

 

l  참고자료

http://java.dzone.com/articles/memory-leak-protection-tomcat

http://java.dzone.com/articles/mark-thomas-apache-tomcat-7

http://tomcat.apache.org/tomcat-6.0-doc/changelog.html

http://wiki.apache.org/tomcat/MemoryLeakProtection?action=diff&rev1=3&rev2=4

 

 

'java core' 카테고리의 다른 글

Desktop JVM 보안 문제  (0) 2010.04.17
문제 해결 가이드 링크  (0) 2010.04.15
-XX:+AggressiveOpts -XX:+DoEscapeAnalysis 테스트  (0) 2010.03.02
JVM crash..  (0) 2010.02.25
Exception 이름과 실제 내용만 찍기  (0) 2010.02.25
Posted by '김용환'
,


1. 테스트 계획

톰캣 2개에 아래 JVM 옵션을 추가하여 cpu 확인

JVM 옵션 추가 :  -XX:+AggressiveOpts -XX:+DoEscapeAnalysis 
 java version : java 1.6.0 13 release

 

2. 결과

 Thread-safe 한 instance 기반의 Webwork와 Spring 기반이기 때문에 특별히 성능 효과을 기대할 수 없었습니다.

 

 해당 기간 동안의 평균 cpu 정보

테스트 서버 : cpu - 8.33

기존 서버 : cpu - 8.27~8.30

 

3. 옵션 내용

 

-XX:+AggressiveOpts

http://java.sun.com/performance/reference/whitepapers/tuning.html

 

-XX:+DoEscapeAnalysis

 http://blog.xebia.com/2007/12/21/did-escape-analysis-escape-from-java-6/

 

 

4. 참고 내용

 

 참고) Crash 현상 발생

(java 1.6.0_06)에 적용했다가 JVM 서버가 Crash 현상 발생 (aggresiveopts 함부로 쓰지 말 것을 배움)

 

Posted by '김용환'
,

JVM crash..

java core 2010. 2. 25. 19:17


java version "1.6.0_06"에서 JVM에 AggresiveOption 옵션넣다가 죽다..ㅡ.ㅡ;;;
음.. 우리웹이랑 안맞나봐..

#
# An unexpected error has been detected by Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x0630a860, pid=12551, tid=2293226400
#
# Java VM: Java HotSpot(TM) Server VM (10.0-b22 mixed mode linux-x86)
# Problematic frame:
# V  [libjvm.so+0x30a860]
#
# An error report file with more information is saved as:
# /home/www/script/hs_err_pid12551.log
#
# If you would like to submit a bug report, please visit:
#   http://java.sun.com/webapps/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#
Posted by '김용환'
,