實例詳解Java中ThreadLocal內存洩漏。本站提示廣大學習愛好者:(實例詳解Java中ThreadLocal內存洩漏)文章只能為提供參考,不一定能成為您想要的結果。以下是實例詳解Java中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
洩露。每次你 reload
一下運用,就會多一份WebappClassLoader
實例,最初招致 PermGen OutOfMemoryException
。
處理成績
如今我們來思慮一下:為何下面的ThreadLocal
子類會招致內存洩露?
WebappClassLoader
起首,我們要弄清晰WebappClassLoader
是甚麼鬼?
關於運轉在 Java EE容器中的 Web 運用來講,類加載器的完成方法與普通的 Java 運用有所分歧。分歧的 Web 容器的完成方法也會有所分歧。以 Apache Tomcat 來講,每一個 Web 運用都有一個對應的類加載器實例。該類加載器也應用署理形式,所分歧的是它是起首測驗考試去加載某個類,假如找不到再署理給父類加載器。這與普通類加載器的次序是相反的。這是 Java Servlet 標准中的推舉做法,其目標是使得 Web 運用本身的類的優先級高於 Web 容器供給的類。這類署理形式的一個破例是:Java 焦點庫的類是不在查找規模以內的。這也是為了包管 Java 焦點庫的類型平安。
也就是說WebappClassLoader
是 Tomcat 加載 webapp 的自界說類加載器,每一個 webapp 的類加載器都是紛歧樣的,這是為了隔離分歧運用加載的類。
那末WebappClassLoader
的特征跟內存洩露有甚麼關系呢?今朝還看不出來,然則它的一個很主要的特色值得我們留意:每一個 webapp 都邑本身的WebappClassLoader
,這跟 Java 焦點的類加載器紛歧樣。
我們曉得:招致WebappClassLoader
洩露必定是由於它被其余對象強援用了,那末我們可以測驗考試畫出它們的援用關系圖。等等!類加載器的感化究竟是啥?為何會被強援用?
類的性命周期與類加載器
要處理下面的成績,我們得去研討一下類的性命周期和類加載器的關系。
跟我們這個案例相干的重要是類的卸載:
在類應用完以後,假如知足上面的情形,類就會被卸載:
1、該類一切的實例都曾經被收受接管,也就是 Java 堆中不存在該類的任何實例。
2、加載該類的ClassLoader
曾經被收受接管。
3、該類對應的java.lang.Class
對象沒有任何處所被援用,沒有在任何處所經由過程反射拜訪該類的辦法。
假如以上三個前提全體知足,JVM 就會在辦法區渣滓收受接管的時刻對類停止卸載,類的卸載進程其實就是在辦法區中清空類信息,Java 類的全部性命周期就停止了。
由Java虛擬機自帶的類加載器所加載的類,在虛擬機的性命周期中,一直不會被卸載。Java虛擬機自帶的類加載器包含根類加載器、擴大類加載器和體系類加載器。Java虛擬機自己會一直援用這些類加載器,而這些類加載器則會一直援用它們所加載的類的Class對象,是以這些Class對象一直是可觸及的。
由用戶自界說的類加載器加載的類是可以被卸載的。
留意下面這句話,WebappClassLoader
假如洩露了,意味著它加載的類都沒法被卸載,這就說明了為何下面的代碼會招致 PermGen OutOfMemoryException
。
症結點看上面這幅圖
我們可以發明:類加載器對象跟它加載的 Class 對象是雙向聯系關系的。這意味著,Class 對象能夠就是強援用WebappClassLoader
,招致它洩露的首惡。
援用關系圖
懂得類加載器與類的性命周期的關系以後,我們可以開端畫援用關系圖了。(圖中的LeakingServlet.class
與myThreadLocal
援用畫的不嚴謹,重要是想表達myThreadLocal
是類變量的意思)
上面,我們依據下面的圖來剖析WebappClassLoader
洩露的緣由。
1、LeakingServlet
持有static
的MyThreadLocal
,招致myThreadLocal
的性命周期跟LeakingServlet
類的性命周期一樣長。意味著myThreadLocal
不會被收受接管,弱援用形同虛設,所以以後線程沒法經由過程ThreadLocalMap
的防護辦法消除counter的強援用。
2、強援用鏈:thread -> threadLocalMap -> counter -> MyCounter.class -> WebappClassLocader
,招致WebappClassLoader
洩露。
總結
內存洩露是很難發明的成績,常常因為多方面緣由形成。ThreadLocal因為它與線程綁定的性命周期成了內存洩露的常客,稍有失慎就變成年夜禍。本文只是對一個特定案例的剖析,若能以此觸類旁通,那就是極好的。願望本文對年夜家能有所贊助。