앞서 <span class="wp_keywordlink">ThreadLocal</span>
메모리 누수 문제에 대한 이론적 분석인 ThreadLocal 메모리 누수 문제에 대한 심층 분석을 작성했습니다. 이번 글에서는 실제 메모리 누수를 분석해 보겠습니다. 사례. 결과보다 문제를 분석하는 과정이 더 중요합니다. 이론과 실제를 결합해야만 메모리 누수의 원인을 철저하게 분석할 수 있습니다.
Tomcat에서는 다음 코드가 모두 webapp에 포함되어 있어 WebappClassLoader
누수를 유발하고 재활용할 수 없습니다.
public class MyCounter { private int count = 0; public void increment() { count++; } public int getCount() { return count; } } public class MyThreadLocal extends ThreadLocal<MyCounter> { } public class LeakingServlet extends HttpServlet { private static MyThreadLocal myThreadLocal = new MyThreadLocal(); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { MyCounter counter = myThreadLocal.get(); if (counter == null) { counter = new MyCounter(); myThreadLocal.set(counter); } response.getWriter().println( "The current thread served this servlet " + counter.getCount() + " times"); counter.increment(); } }
위 코드에서 LeakingServlet
가 한 번 호출되고 이를 실행하는 스레드가 중지되지 않는 한 WebappClassLoader
이 누출됩니다. 애플리케이션을 다시 로드할 때마다 WebappClassLoader
의 추가 인스턴스가 발생하여 결국 PermGen OutOfMemoryException
이 발생합니다.
이제 생각해 보겠습니다. 위의 ThreadLocal
하위 클래스가 메모리 누수를 일으키는 이유는 무엇입니까?
우선, 도대체 WebappClassLoader
가 무엇인지 알아내야 합니다.
Java EE 컨테이너에서 실행되는 웹 애플리케이션의 경우 클래스 로더는 일반 Java 애플리케이션과 다르게 구현됩니다. 다양한 웹 컨테이너는 다르게 구현됩니다. Apache Tomcat의 경우 각 웹 애플리케이션에는 해당 클래스 로더 인스턴스가 있습니다. 이 클래스 로더도 프록시 모드를 사용합니다. 차이점은 먼저 특정 클래스를 로드하려고 시도한 후 상위 클래스 로더로 프록시한다는 것입니다. 이는 일반적인 클래스 로더 순서와 반대입니다. 이는 Java Servlet 사양에서 권장되는 방식이며, 그 목적은 웹 컨테이너에서 제공하는 클래스보다 웹 애플리케이션 자체 클래스에 우선순위를 부여하는 것입니다. 이 프록시 패턴의 예외는 Java 핵심 라이브러리 클래스가 검색에 포함되지 않는다는 것입니다. 이는 Java 코어 라이브러리의 유형 안전성을 보장하기 위한 것이기도 합니다.
즉, WebappClassLoader
은 Tomcat이 웹앱을 로드하는 데 사용하는 사용자 정의 클래스 로더입니다. 이는 각 웹앱의 클래스 로더가 서로 다르기 때문입니다.
그럼 WebappClassLoader
의 기능은 메모리 누수와 어떤 관련이 있나요? 아직 표시되지는 않지만 주목할 만한 매우 중요한 기능이 있습니다. 각 웹앱에는 Java 코어 클래스 로더와 다른 고유한 WebappClassLoader
이 있습니다.
WebappClassLoader
의 유출은 다른 객체에 의해 강력하게 참조되기 때문에 발생한다는 것을 알고 있으므로 해당 참조 관계 다이어그램을 그릴 수 있습니다. 등! 클래스 로더의 역할은 정확히 무엇입니까? 왜 강력하게 인용되는가?
위의 문제를 해결하려면 클래스 라이프사이클과 클래스 로더의 관계를 연구해야 합니다.
저희 사건과 관련된 주요 사항은 클래스 제거입니다.
클래스 사용 후 다음 조건이 충족되면 클래스가 제거됩니다.
이 클래스의 모든 인스턴스가 재활용되었습니다. 즉, Java 힙에 이 클래스의 인스턴스가 없습니다.
이 클래스를 로드한 ClassLoader
이 재활용되었습니다.
이 클래스의 해당 java.<a href="http://www.php.cn/java/java-ymp-Lang.html" target="_blank">lang</a>.Class
객체는 어디에서도 참조되지 않으며, 어디에서도 리플렉션을 통해 이 클래스에 액세스할 수 있는 방법이 없습니다.
위의 세 가지 조건이 모두 충족되면 JVM은 메소드 영역에서 가비지 수집 중에 클래스를 제거합니다. 클래스 제거 프로세스는 실제로 클래스 정보를 지우는 것입니다. 메소드 영역 Java 클래스의 전체 라이프사이클이 끝났습니다.
Java 가상 머신과 함께 제공되는 클래스 로더에 의해 로드된 클래스는 가상 머신의 수명 주기 동안 언로드되지 않습니다. JVM(Java Virtual Machine)과 함께 제공되는 클래스 로더에는 루트 클래스 로더, 확장 클래스 로더 및 시스템 클래스 로더가 포함됩니다. JVM(Java Virtual Machine) 자체는 항상 이러한 클래스 로더를 참조하고 이러한 클래스 로더는 항상 자신이 로드하는 클래스의 클래스 객체를 참조하므로 이러한 클래스 객체에 항상 접근할 수 있습니다.
사용자 정의 클래스 로더에 의해 로드된 클래스는 언로드될 수 있습니다.
위 문장에 주의하세요. WebappClassLoader
이 유출되면 로드한 클래스 중 어느 것도 언로드할 수 없다는 의미이며, 이는 위 코드가 PermGen OutOfMemoryException
을 발생시키는 이유를 설명합니다.
핵심 사항은 아래 그림을 보시면
클래스 로더 객체는 자신이 로드하는 클래스 객체와 양방향으로 연관되어 있음을 알 수 있습니다. 이는 클래스 객체가 강력한 참조 WebappClassLoader
일 수 있어 누출이 발생할 수 있음을 의미합니다.
클래스 로더와 클래스 라이프사이클 간의 관계를 이해한 후 참조 다이어그램 그리기를 시작할 수 있습니다. (사진 속 LeakingServlet.class
, myThreadLocal
의 언급은 정확하지 않으며, 주로 myThreadLocal
가 클래스 변수 임을 표현하기 위한 것입니다.)
下面,我们根据上面的图来分析WebappClassLoader
泄漏的原因。
LeakingServlet
持有static
的MyThreadLocal
,导致myThreadLocal
的生命周期跟LeakingServlet
类的生命周期一样长。意味着myThreadLocal
不会被回收,弱引用形同虚设,所以当前线程无法通过ThreadLocal<a href="http://www.php.cn/code/8210.html" target="_blank">Map</a>
的防护措施清除counter
的强引用。
强引用链:thread -> threadLocalMap -> counter -> MyCounter.class -> WebappClassLocader
,导致WebappClassLoader
泄漏。
内存泄漏是很难发现的问题,往往由于多方面原因造成。ThreadLocal
由于它与线程绑定的生命周期成为了内存泄漏的常客,稍有不慎就酿成大祸。
本文只是对一个特定案例的分析,若能以此举一反三,那便是极好的。最后我留另一个类似的案例供读者分析。
假设我们有一个定义在 Tomcat Common Classpath 下的类(例如说在 tomcat/lib
目录下)
public class ThreadScopedHolder { private final static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>(); public static void saveInHolder(Object o) { threadLocal.set(o); } public static Object getFromHolder() { return threadLocal.get(); } }
两个在 webapp 的类:
public class MyCounter { private int count = 0; public void increment() { count++; } public int getCount() { return count; } } public class LeakingServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { MyCounter counter = (MyCounter)ThreadScopedHolder.getFromHolder(); if (counter == null) { counter = new MyCounter(); ThreadScopedHolder.saveInHolder(counter); } response.getWriter().println( "The current thread served this servlet " + counter.getCount() + " times"); counter.increment(); } }
위 내용은 Java의 ThreadLocal 메모리 누수에 대한 코드 예제 공유(그림)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!