사례 및 분석
문제 배경
Tomcat에서는 다음 코드가 WebApp 내부에 있으므로 WebappClassLoader 누출되어 재활용 할 수 없습니다.
공개 클래스 MyCounter {private int count = 0; public void icrement () {count ++; } public int getCount () {return count; }} public class mythreadlocal 확장 threadLocal <MyCounter> {} public class LeakingServlet는 httpservlet {private static mythreadlocal mythreadlocal = new MythreadLocal (); 보호 된 void doget (httpservletRequest 요청, httpservletResponse 응답) servletexception, ioexception {myCounter counter = mythreadlocal.get (); if (counter == null) {counter = new MyCounter (); mythreadlocal.set (counter); } response.getWriter (). println ( "현재 스레드 가이 서블릿을 제공했습니다" + counter.getCount () + "times"); counter.increment (); }} 위의 코드에서, LeakingServlet 한 번 호출되고 실행 된 스레드가 중지되지 않는 한 WebappClassLoader 누출이 발생합니다. 응용 프로그램을 reload 때마다 추가 WebappClassLoader 인스턴스가있어 결국 PermGen OutOfMemoryException 됩니다.
문제를 해결하십시오
이제 그것에 ThreadLocal 생각해 봅시다.
WebAppClassLoader
우선, 우리는 WebappClassLoader 가 무엇인지 알아 내야합니까?
Java EE 컨테이너에서 실행되는 웹 응용 프로그램의 경우 클래스 로더 구현은 일반적인 Java 응용 프로그램의 구현과 다릅니다. 다른 웹 컨테이너도 다르게 구현됩니다. Apache Tomcat에서 각 웹 응용 프로그램에는 해당 클래스 로더 인스턴스가 있습니다. 이 클래스 로더는 또한 프록시 모드를 사용합니다. 차이점은 먼저 특정 클래스를로드하려고 시도한 다음 부모 클래스 로더를 찾을 수없는 경우 프록시를 프록시한다는 것입니다. 이것은 일반 클래스 로더의 순서와 반대입니다. 이것은 Java Servlet 사양에서 권장되는 관행이며, 그 목적은 웹 응용 프로그램의 클래스가 웹 컨테이너에서 제공 한 것보다 우선 순위를 정하는 것입니다. 이 프록시 패턴의 예외는 Java Core 라이브러리의 클래스가 검색 범위 내에 있지 않다는 것입니다. 이것은 또한 Java Core 라이브러리의 유형 안전을 보장하기위한 것입니다.
다시 말해, WebappClassLoader Tomcat이 WebApp을로드하기위한 사용자 정의 클래스 로더입니다. 각 WebApp의 클래스 로더는 다릅니다. 이것은 다른 응용 프로그램에 의해로드 된 클래스를 분리하기위한 것입니다.
그렇다면 WebappClassLoader 의 기능은 메모리 누출과 어떤 관련이 있습니까? 아직 보이지는 않지만 우리의 관심을 가질 가치가있는 것은 매우 중요한 기능입니다. 각 Webapp에는 자체 WebappClassLoader 가 있으며 Java Core Class 로더와 다릅니다.
WebappClassLoader 유출은 다른 객체에 의해 강력하게 참조되기 때문에 참조 관계 다이어그램을 그릴 수 있기 때문입니다. 등! 클래스 로더는 정확히 무엇을 작동합니까? 왜 인용해야합니까?
클래스 수명주기 및 클래스 로더
위의 문제를 해결하려면 클래스의 수명주기와 클래스 로더 간의 관계를 연구해야합니다.
우리의 사건과 관련된 가장 중요한 것은 클래스 제거입니다.
수업이 사용 된 후 다음 상황이 충족되면 수업이 제거됩니다.
1.이 클래스의 모든 사례는 재활용되지 않았습니다. 즉,이 클래스의 사례는 Java 힙에 존재하지 않습니다.
2.이 클래스를로드하는 ClassLoader 재활용되었습니다.
3.이 클래스에 해당하는 java.lang.Class 객체는 어느 곳에서나 참조되지 않으며 어디서나 반사를 통해 클래스에 액세스 할 수있는 방법이 없습니다.
위의 세 가지 조건이 모두 충족되면 JVM은 방법 영역에서 쓰레기 수집 중에 클래스를 제거합니다. 클래스 제거 프로세스는 실제로 메소드 영역에서 클래스 정보를 지우는 것입니다.
Java Virtual Machine과 함께 제공되는 클래스 로더가로드 한 클래스는 가상 머신의 수명주기 동안 제거되지 않습니다. Java 가상 머신과 함께 제공되는 클래스 로더에는 루트 클래스 로더, 확장 클래스 로더 및 시스템 클래스 로더가 포함됩니다. Java Virtual Machine 자체는 항상 이러한 클래스 로더를 참조하고,이 클래스 로더는 항상로드 클래스의 클래스 객체를 참조하므로 이러한 클래스 객체는 항상 액세스 할 수 있습니다.
사용자 정의 클래스 로더가로드 한 클래스를 내릴 수 있습니다.
위의 문장에 주목하십시오. WebappClassLoader 누출되면,로드 클래스를 제거 할 수 없음을 의미하므로 위의 코드가 왜 PermGen OutOfMemoryException 유발하는지 설명합니다.
핵심 지점에서 아래 그림을보십시오
클래스 로더 객체가로드하는 클래스 객체와 양방향으로 연관되어 있음 을 알 수 있습니다 . 이는 클래스 객체가 WebappClassLoader 에 대한 강제 참조의 범인 일 수 있음을 의미합니다.
견적 관계 다이어그램
클래스 로더와 클래스의 수명주기 사이의 관계를 이해 한 후 참조 관계 다이어그램을 그리기 시작할 수 있습니다. (그림의 LeakingServlet.class 및 myThreadLocal 은 참조 도면에서 엄격하지 않으며, 주로 myThreadLocal 이 클래스 변수라는 것을 표현하기 위해)
아래에서는 위의 다이어그램을 기반으로 WebappClassLoader 누출의 원인을 분석합니다.
1. LeakingServlet static MyThreadLocal 유지하여 LeakingServlet 클래스의 수명주기까지 myThreadLocal 의 수명주기를 초래합니다. 이는 myThreadLocal 재활용되지 않으며 약한 참조가 쓸모가 없으므로 현재 스레드는 ThreadLocalMap 의 보호 조치를 통해 카운터의 강력한 참조를 지울 수 없습니다.
2. 강력한 참조 체인 : thread -> threadLocalMap -> counter -> MyCounter.class -> WebappClassLocader , WebappClassLoader 누출되게합니다.
요약
메모리 누출은 감지하기 어렵고 종종 여러 가지 이유로 인해 발생합니다. Threadlocal은 실에 바인딩 된 수명 주기로 인해 메모리 누출을 자주 방문했으며 약간 부주의하게 재난으로 이어질 것입니다. 이 기사는 특정 사례에 대한 분석 일뿐입니다. 이것을 사용하여 다른 경우에 적용 할 수 있다면 우수합니다. 이 기사가 모두에게 도움이되기를 바랍니다.