Apache Tomcat6에서 구동하던 웹 애플리케이션에서 다음과 같은 에러를 발견하였다.
심각: The web application [/ AP_NAME ] registered the JDBC driver [com.mysql.jdbc.Driver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.
2012. 7. 12 오후 1:58:59 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
심각: The web application [/ AP_NAME ] appears to have started a thread named [MySQL Statement Cancellation Timer] but has failed to stop it. This is very likely to create a memory leak.
2012. 7. 12 오후 1:58:59 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
심각: The web application [/AP_NAME] appears to have started a thread named [pool-1-thread-3] but has failed to stop it. This is very likely to create a memory leak.
이 에러는 톰캣을 시작하거나 종료할 때는 발생하지 않는다.
오직 톰캣을 완벽하게 내리지 않고, 해당 애플리케이션을 재시작하였을 때에만 발생한다.
운영에 문제는 없다. 그러나 발견한 에러를 그냥 두는 것은 상당히 찜찜한 일이다. 그러므로 오늘 해결을 위해 조사 및 테스트를 해보았다.
위에서는 3개의 에러가 났다. JDBC와 관련되어서 2개가 발생하였고, 세번째는 ThreadPool에서 관리하는 쓰레드를 중지하지 못해서 난 것이다. 톰캣에서 발생한 이 메모리 누수의 발생 이유는 김용환 블로그[1]에 잘 나와 있었다.
그 이유는 다음과 같다.
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 |
김용환 블로그[1]에서 발췌.
내가 얻은 3개의 에러도 위의 내요에 포함한다. A JDBC Driver와 D. 쓰레드를 시작시키고 멈추지 않아서이다.
우선 A를 해결하는 방법은 다음에 설명하겠다.(아직 여기까지는 하지 못했다.)
"D. 쓰레드를 시작시키고 멈추지 않아서"로 발생한 에러를 해결하는 방법에 대해서 써보겠다.
.
우선 Java에서 쓰레드에 대해 얘기해보자.
Java에서 쓰레드는 Thread클래스를 상속 받거나 Runnable 인터페이스를 구현해서 만들어진다. 그리고 Thread클래스의 start()메소드를 통해 시작하고 stop()메소드를 통해 중지되어진다.
그러나 한 두개의 쓰레드를 쓸 경우가 아니라면, 쓰레드를 관리하기 위해서 ThreadPool을 사용한다.(비단 java뿐만 아니라.)
내가 얻은 문제는 이 ThreadPool을 사용하면서 발생하였다.
난 웹 애플리케이션의 종료시에 ThreadPool에서 관리하는 쓰레드들을 shutdownNow메소드를 이용하여 중지시켰다. 그러나 shutdownNow메소드는 내부에서 관리되는 쓰레드들을 중지시키지 못했다.
왜일까?
우선 Thread의 stop메소드와 ExecutorService의 shutdownNow에는 차이가 있다.
stop메소드는 쓰레드를 무조건 중지시킨다. 설령 내부에 멈출 수 없는 무한반복( while(true)와 같은)이 있다고 하더라도 중지시킨다.
그러나, ExecutorService의 shutdownNow는 그렇지 못한다. shutdownNow는 내부 관리되고 있는 쓰레드들에게 인터럽트를 호출해줄뿐이다. (직접 소스를 까보니 그렇더라..)
그리고 이것이 쓰레드가 종료되지 않은 이유이다.
왜냐하면 나는 shutdownNow 메소드가 stop메소드와 같이 중지가 될꺼라고 생각을 하고 쓰레드 내부에 while(true)와 같은 무한반복문을 넣었던 것이다. 그리고 내부에는 인터럽트 되어 지지 않는 sleep과 같은 메소드를 통해서 대기를 하고 있었다.
그러므로 ThreadPool을 통해 사용되어지는 쓰레드는 일반 쓰레드를 구현할 때와 달라야만 한다.
조사와 실험과 개발을 통해 얻은 나의 결론을 적어보겠다.
적으면서 어렴풋히 이전에 본 자바 병행 프로그래밍 책에서 공부한 내용이 떠오른다. 역시 병행 프로그래밍은 그리 만만한 부분이 아니다...후훗..
Java ThreadPool에서 쓰는 쓰레드 설계시 유의사항
1. 무한반복문
어떤 형태로든지 쓰레드가 끝나면 상관 없지만, 웹 애플리케이션과 생명주기를 같이 하는 쓰레드라면 무한반복을 하게 된다. 그런데 이 때 while(true)와 같이 사용하면 안된다. 이 부분은 자바 병행 프로그래밍에 대하여 공부하면 나오는 부분이다. 내가 간과했었다.
while(isInterrupted()) 와 같이 사용해야 한다.
Runnable의 구현체라면 while(Thread.currentThread().isInterrupted()) 와 같이 사용해야 한다.
2. InterruptedException catch
InterruptedException이 무엇이냐면 외부에서 쓰레드에 대하여 인터럽트를 발생시켰는데 쓰레드가 특정 상태에 빠져서 인터럽트가 걸리지 않아서 발생하는 예외이다.
대표적인 것이 Thread클래스의 sleep메소드이다.
다른 예로는 내가 사용한 LinkedBlockingQueue의 take메소드이다. take메소드는 큐가 비어있다면 큐에 새로운 객체가 들어오기 전까지 대기하게 된다. 즉 sleep메소드처럼 대기하게 되고 이 상태에서는 인터럽트가 걸리지 않게 되는 것이다.
그러므로 InterruptedException이 발생할 수 있는 메소드는 반드시 잡아내어서(catch) 중지해야 한다.
.
위의 두 가지에 대한 간단한 코드는 다음과 같다.
while(!isInterrupted()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
} catch (Exception e) {
...
}
}
나와 같은 실수를 하지 않길 바라며, Good luck
Reference
[1] 김용환 블로그 (http://knight76.tistory.com/947)
'Devlopment > Java' 카테고리의 다른 글
JBoss 설치 및 연동 (0) | 2013.04.19 |
---|---|
Mavn 및 플러그인 설치 (0) | 2013.04.19 |
Java 이전 버전을 받을 수 있는 URL (0) | 2013.03.28 |
Java 실행 (0) | 2013.01.31 |
Tomcat & Java의 memory leak 분석 (0) | 2012.07.17 |
자바 기본 메소드의 구현 (0) | 2012.06.20 |
MyBatis NumberFormatException 문제 (0) | 2012.05.26 |
Java간의 통신 (1) | 2012.05.10 |
Netty서버에서 1024이상 받지 못하는 문제의 해결법 (0) | 2012.04.15 |
XmlGenerator - Java에서 XML 생성 (3) | 2012.03.09 |