Java中ThreadLocal記憶體洩漏的程式碼實例分享(圖)
前言
之前寫了一篇深入分析ThreadLocal 記憶體洩漏問題是從理論上分析<span class="wp_keywordlink">ThreadLocal</span>
的記憶體洩漏問題,這篇文章我們來分析一下實際的記憶體洩漏案例。分析問題的過程比結果更重要,理論結合實際上才能徹底分析記憶體洩漏的原因。
案例與分析
問題背景
在 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
洩漏必然是因為它被別的物件強引用了,那麼我們可以試著畫出它們的引用關係圖。等等!類別載入器的作用到底是啥?為什麼會被強引用?
類別的生命週期與類別載入器
要解決上面的問題,我們得去研究一下類別的生命週期和類別載入器的關係。
跟我們這個案例相關的主要是類別的卸載:
在類別使用完之後,如果滿足下面的情況,類別就會被卸載:
-
#該類別所有的實例都已經被回收,也就是Java 堆中不存在該類別的任何實例。
載入該類別的
ClassLoader
已經被回收。該類別對應的
java.<a href="http://www.php.cn/java/java-ymp-Lang.html" target="_blank">lang</a>.Class
物件沒有任何地方被引用,沒有在任何地方透過反射來存取該類別的方法。
如果以上三個條件全部滿足,JVM 就會在方法區垃圾回收的時候對類別進行卸載,類別的卸載過程其實就是在方法區中清空類別訊息,Java類別的整個生命週期就結束了。
由Java虛擬機器自帶的類別載入器所載入的類,在虛擬機器的生命週期中,始終不會被卸載。 Java虛擬機器自帶的類別載入器包括根類別載入器、擴充類別載入器和系統類別載入器。 Java虛擬機本身會始終引用這些類別載入器,而這些類別載入器則會始終引用它們所載入的類別的Class對象,因此這些Class物件始終是可觸及的。
由使用者自訂的類別載入器載入的類別是可以被卸載的。
注意上面這句話,WebappClassLoader
如果洩漏了,表示它載入的類別都無法被卸載,這就解釋了為什麼上面的程式碼會導致PermGen OutOfMemoryException
。
關鍵點看下面這張圖
我們可以發現:類別載入器物件跟它載入的 Class 物件是雙向關聯的。這意味著,Class 物件可能是強引用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中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

Java 8引入了Stream API,提供了一種強大且表達力豐富的處理數據集合的方式。然而,使用Stream時,一個常見問題是:如何從forEach操作中中斷或返回? 傳統循環允許提前中斷或返回,但Stream的forEach方法並不直接支持這種方式。本文將解釋原因,並探討在Stream處理系統中實現提前終止的替代方法。 延伸閱讀: Java Stream API改進 理解Stream forEach forEach方法是一個終端操作,它對Stream中的每個元素執行一個操作。它的設計意圖是處

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

PHPhassignificantlyimpactedwebdevelopmentandextendsbeyondit.1)ItpowersmajorplatformslikeWordPressandexcelsindatabaseinteractions.2)PHP'sadaptabilityallowsittoscaleforlargeapplicationsusingframeworkslikeLaravel.3)Beyondweb,PHPisusedincommand-linescrip

PHP成為許多網站首選技術棧的原因包括其易用性、強大社區支持和廣泛應用。 1)易於學習和使用,適合初學者。 2)擁有龐大的開發者社區,資源豐富。 3)廣泛應用於WordPress、Drupal等平台。 4)與Web服務器緊密集成,簡化開發部署。

PHP適用於Web開發和內容管理系統,Python適合數據科學、機器學習和自動化腳本。 1.PHP在構建快速、可擴展的網站和應用程序方面表現出色,常用於WordPress等CMS。 2.Python在數據科學和機器學習領域表現卓越,擁有豐富的庫如NumPy和TensorFlow。
