本文將為您提供一個教程,使您可以確定活動應用程序Java線程保留Java堆空間的數量和位置。 將提供來自Oracle Weblogic 10.0生產環境的真實案例研究,以使您更好地理解分析過程。
我們還將嘗試證明過多的垃圾回收或Java堆空間的內存占用問題通常不是由真正的內存泄漏引起的,而是由線程執行模式和大量的短期對象引起的。
背景
您可能從我過去的JVM概述文章中看到,Java線程是JVM基礎的一部分。 您的Java堆空間內存占用量不僅受靜態對象和壽命長的對象的驅動,還受壽命短的對象的驅動。
通常會錯誤地認為OutOfMemoryError問題是由于內存泄漏引起的。 我們經常忽略錯誤的線程執行模式和它們在Java堆上“保留”到執行完成的短暫對象。 在這種有問題的情況下:
- 您的“預期”應用程序的短期/無狀態對象(XML,JSON數據有效載荷等)被線程保留的時間過長(線程鎖爭用,巨大的數據有效載荷,來自遠程系統的響應時間慢等)。
- 最終,這樣的短期對象被垃圾收集器提升為長期對象空間,例如OldGen / tenured空間
- 副作用是,這導致OldGen空間快速填充,從而增加了Full GC(主要集合)的頻率
- 根據情況的嚴重性,這可能導致過度的GC垃圾收集,JVM暫停時間增加,并最終導致OutOfMemoryError:Java堆空間
- 您的應用程序現在關閉,您現在對正在發生的事情感到困惑
- 最后,您正在考慮增加Java堆或尋找內存泄漏……您是否真的走對了?
在上述情況下,您需要查看線程執行模式,并確定它們在給定時間保留多少內存。
好了,我得到了圖片,但是線程堆棧的大小呢?
避免線程堆棧大小和Java內存保留之間的混淆是非常重要的。 線程堆棧大小是JVM用于存儲每個方法調用的特殊內存空間。 當線程調用方法A時,它將調用“推”到堆棧上。 如果方法A調用方法B,它也會被壓入堆棧。 一旦方法執行完成,調用便從堆棧中“彈出”。
由于此類線程方法調用而創建的Java對象在Java堆空間上分配。 絕對增加線程堆棧大小不會有任何效果。 處理java.lang.stackoverflowerror或OutOfMemoryError:無法創建新的本機線程問題時,通常需要調整線程堆棧的大小。
案例研究和問題背景
以下分析基于我們最近調查的真實生產問題。
- 在用戶Web界面進行了一些更改(使用Google Web Toolkit和JSON作為數據有效負載)之后,從Weblogic 10.0生產環境中觀察到嚴重的性能下降。
- 初步分析確實揭示了OutOfMemoryError的幾種情況:Java堆空間錯誤以及過多的垃圾回收。 在發生OOM事件后,會自動生成Java堆轉儲文件(-XX:+ HeapDumpOnOutOfMemoryError)
- 對詳細:gc日志的分析確實確認了32位HotSpot JVM OldGen空間(1 GB容量)的完全耗盡
- 在問題發生之前和過程中也生成了線程轉儲快照
- 當時唯一可以解決的問題是觀察到問題時重新啟動受影響的Weblogic服務器
- 最終對變更進行了回滾,這確實解決了這種情況
團隊首先從引入的新代碼中懷疑了內存泄漏問題。
線程轉儲分析:尋找可疑對象…
我們所做的第一步是對生成的線程轉儲數據進行分析。 線程轉儲通常會向您顯示在Java堆上分配內存的罪魁禍首線程。 它還將揭示試圖從遠程系統發送和接收數據有效載荷的任何占用線程或阻塞線程。
我們注意到的第一個模式是從Weblogic托管服務器(JVM進程)觀察到的OOM事件和STUCK線程之間具有良好的相關性。 在找到的主線程模式下面找到:
<10-Dec-2012 1:27:59 o'clock PM EST> <Error> <BEA-000337><[STUCK] ExecuteThread: '22' for queue:'weblogic.kernel.Default (self-tuning)'has been busy for '672' seconds working on the requestwhich is more than the configured time of '600' seconds.
如您所見,以上線程似乎是STUCK或花費很長時間讀取和接收來自遠程服務器的JSON響應。 一旦找到該模式,下一步就是將該發現與JVM堆轉儲分析相關聯,并確定這些卡住的線程從Java堆中占用了多少內存。
堆轉儲分析:保留的對象暴露在外!
Java堆轉儲分析是使用MAT執行的。 現在,我們將列出不同的分析步驟,這些步驟確實使我們可以查明保留的內存大小和源。
1.加載HotSpot JVM堆轉儲
2.選擇HISTOGRAM視圖并按“ ExecuteThread”進行過濾
* ExecuteThread是Weblogic內核用于線程創建和執行的Java類*
如您所見,這種觀點非常明顯。 我們可以看到總共創建了210個Weblogic線程。 這些線程保留的內存總量為806 MB。 這對于具有1 GB OldGen空間的32位JVM進程而言非常重要。 僅此觀點就告訴我們問題的核心和內存保留源于線程本身。
3.深入研究線程內存占用量分析
下一步是深入研究線程內存保留。 為此,只需右鍵單擊ExecuteThread類,然后選擇:列表對象>帶有傳出引用。
如您所見,我們能夠將線程轉儲分析中的STUCK線程與堆轉儲分析中的高內存保留量相關聯。 這個發現非常令人驚訝。
4.線程Java局部變量識別
最后的分析步驟確實需要我們擴展一些線程示例并了解內存保留的主要來源。
如您所見,這最后一個分析步驟確實從根本原因上揭示了巨大的JSON響應數據有效載荷。 該模式還通過線程轉儲分析在早期公開,我們發現一些線程需要很長時間才能讀取和接收JSON響應。 數據有效負載占用量巨大的明顯癥狀。
至關重要的是要注意,通過局部方法變量創建的短期對象將顯示在堆轉儲分析中。 但是,其中一些將僅在其父線程中可見,因為這種情況下其他對象未引用它們。 您還需要分析線程堆棧跟蹤以識別真正的調用者,然后進行代碼檢查以確認根本原因。
根據這一發現,在某些情況下,我們的交付團隊能夠確定最近的JSON錯誤代碼更改正在生成高達45 MB +的巨大JSON數據有效負載。 考慮到該環境使用的是只有1 GB OldGen空間的32位JVM,您可以理解,只有幾個線程足以觸發嚴重的性能下降。
該案例研究清楚地表明了適當的容量規劃和Java堆分析的重要性,包括從活動應用程序和Java EE容器線程中保留的內存。
其他一切都只是信息
我希望本文能幫助您了解如何通過結合線程轉儲和堆轉儲分析來確定活動線程保留的Java堆內存占用量。 現在,如果您不嘗試的話,本文將僅停留在文字上,因此,我強烈建議您花一些時間自己學習針對您的應用程序進行的分析過程。
參考: Java Thread:保留了我們的JCG合作伙伴 Pierre-Hugues Charbonneau在Java EE支持模式和Java教程博客上進行的內存分析 。
翻譯自: https://www.javacodegeeks.com/2012/12/java-thread-retained-memory-analysis.html