1. 程式人生 > >《JVM故障診斷指南》之3 —— Java 執行緒: JVM持有記憶體的分析

《JVM故障診斷指南》之3 —— Java 執行緒: JVM持有記憶體的分析

原文連結 原文作者:Byron Kiourtzoglou 翻譯:梅小西(904516706)

前面我們已經討論過JVM裡不同的堆空間,這節我們會給你提供教程,是關於如何從你的活動的應用Java執行緒中確定它持有多少堆空間,以及在哪裡佔用。這裡有個來自Oracle Weblogic 10.0生產環境的真實案例,它能使你更好的理解分析過程。

我們也會演示這種情況,過多的垃圾收集或者堆空間記憶體佔用問題並不總是由於真實的記憶體洩露引起,也可能是由於執行緒執行模型問題和太多的短生命物件引起。

後臺

Java執行緒是JVM基礎的一部分。你的Java堆空間記憶體佔用不僅僅是由於靜態的和長生命的物件導致,還有可能因為短生命物件。

OutOfMemoryError 問題經常被誤認為是記憶體洩露引起。我們經常忽略錯誤的執行緒執行模型和它們持有的JVM裡的短生命物件,直到它們的執行完成我們才發現。在這種問題情形下:

• 你“預期”的程式中短生命/無狀態物件(XML,JSON資料負載等)被執行緒持有的時間會變得很長(執行緒鎖爭用,大量資料負載,遠端系統的慢響應時間等)。
• 最後,這種短生命物件會因為垃圾收集而晉升到長生命空間,比如老年代空間。
• 副作用是會導致老年代空間很快被佔滿,增加了Full GC(major 收集)的頻率。
• 由於這種嚴重的情況,它將導致更多的GC 垃圾收集,增加JVM暫停時間和最終的“OutOfMemoryError: Java 堆空間”。
• 你的應用此時被停掉,你很疑惑到底怎麼回事。
• 最後,你考慮增加Java堆空間或者尋找哪裡有記憶體洩露,你真的找對路了麼?

上面這種情況,你應該找到執行緒執行模型並確定在給定的時間內每個執行緒需要持有多少記憶體。

OK我找到了這張圖片,但是執行緒棧大小到底是多少呢?

避免線上程棧大小和Java堆記憶體佔用之間產生混淆是非常重要的。執行緒棧大小是一種特殊的記憶體空間,它被JVM用於儲存每個方法呼叫。當一個執行緒呼叫方法A,它將這個呼叫入棧。如果方法A呼叫方法B,同樣也會入棧。一旦方法執行完畢,這個呼叫便從棧裡出棧。

這種執行緒方法呼叫會導致Java物件產生,並分配在Java堆裡。增加執行緒棧的大小是沒有任何效果的。而調整執行緒棧大小通常是要處理java.lang.stackoverflowerror錯誤或者“OutOfMemoryError: unable to create new native thread”錯誤的時候才會需要。

這裡寫圖片描述
案例研究和問題環境

下面的分析是基於我們最近調查的一個真實的生產線問題。
1. 改變了使用者web介面(使用Google Web Toolkit 和 JSON作為資料負載)後,發現Weblogic 10.0生產環境上出現了某些效能下降。
2. 初始分析發現出現了“OutOfMemoryError: Java heap space”問題並伴有過多的垃圾收集。在OOM出現後自動(XX:+HeapDumpOnOutOfMemoryError)生成了Java堆轉儲檔案。
3. 通過verbose:gc 日誌分析確認32-bit HotSpot JVM 老年代空間(1 GB 容量)被完全消耗。
4. 問題發生前和發生時自動產生了執行緒轉儲快照。
5. 此時唯一可能減輕問題的方法是在問題發生時重啟受影響的Weblogic 伺服器。
6. 最終解決了這個問題是將所有的改變回滾。
7.
團隊首先懷疑新程式碼引起了記憶體洩露。

執行緒轉儲分析:尋找嫌疑

第一步我們要做的是對產生的執行緒轉儲資料進行分析。這種資料通常會告訴你在JVM堆裡面記憶體分配的罪魁禍首執行緒。同樣的它也會顯示任何一個嘗試從遠端系統傳送和接受資料的貪婪或者阻塞的執行緒。

我們注意的第一個樣例是在Weblogic 控制伺服器(JVM執行緒)裡觀察到的OOM事件和阻塞執行緒之間有很近的關聯關係。下面是找到的原始執行緒模式:
這裡寫圖片描述

000337> < [STUCK] ExecuteThread: '22' for queue: 'weblogic.kernel.Default (self-tuning)' has been busy for "672" seconds working on the request which is more than the configured time of "600" seconds.

如你所見,上面的執行緒出現了阻塞並且花費了很長時間從遠端系統裡讀和接收JSON響應。一旦我們找到這個樣例,接下來是找出它和JVM堆轉儲分析之間的關聯,並且確定這個阻塞執行緒從堆裡佔用了多少記憶體。

堆轉儲分析:暴露留存的物件!

使用MAT工具來做Java堆轉儲分析。我們會列出不同的分析步驟,它會允許我們精確查明持有的記憶體大小和源頭。

1.載入HotSpotJVM堆轉儲檔案

這裡寫圖片描述

2.選擇HISTOGRAM 檢視並通過ExecuteThread過濾。
* ExecuteThread 是一種Java Class,它被Weblogic核心用於物件的建立和執行*
這裡寫圖片描述
如你所見,這個圖非常有啟迪作用。我們可以看到總共210個Weblogic執行緒被建立。
這些執行緒總共持有的記憶體佔用是806M。這對於帶有1G 老年代的32位的JVM程序來說是非常值得注意的。這個圖也告訴了我們這個問題的核心和源於執行緒自己的記憶體佔有。

3.深入分析執行緒記憶體佔用

接下來是深入分析執行緒記憶體持有。右鍵點選ExecuteThread 類並且選擇“列出所有外部引用的物件”。
這裡寫圖片描述
如你所見,我們通過執行緒堆轉儲分析可以發現“STUCK(阻塞)”執行緒和大量記憶體佔用有很大關係。這個發現非常意外。

4.執行緒區域性變數鑑別Thread Java Local variables identification

分析的最後一步是需要我們展開幾個執行緒示例並瞭解記憶體佔用的原始來源。

這裡寫圖片描述

如你所見,最後一步分析發現根源在於大量的JSON資料響應。通過對轉儲分析,這個問題可以早點暴露出來,我們發現少量的執行緒花費太多時間去讀取和接收JSON響應,這是大量資料負載的一個明顯的症狀。

很重要一點,通過方法區域性變數建立的短生命物件也會出現在堆轉儲分析中。然而,其中的一些僅僅能被他們的父執行緒看到,這是由於他們沒有被其他物件引用,比如這個例子。為了找出真正的呼叫者你或許也需要分析這個執行緒棧,隨後通過程式碼審查確定最終的根源。

通過這些發現,我們的交付團隊能確定最近的JSON錯誤程式碼變化的產生,在某些情形下,大量的JSON資料可以達到45M以上。如果環境使用了32位JVM而且僅僅只有1G的老年代,基於這個事實,你就能理解為什麼只需要幾個執行緒就足夠觸發一些效能下降。

這個案例說明,合適的容量預計和堆分析,包括你活動的應用程式的記憶體佔有和Java EE容器執行緒記憶體佔有都是非常重要的。

譯者介紹:
梅小西
Java工程師,關注JVM,併發程式設計,喜歡研究Python,Scala,Golang等。