AIX 平臺上基於 IBM JDK 的 Java 應用內存泄漏分析

AIX 平臺上基於 IBM JDK 的 Java 應用內存泄漏分析

本文將主要面向 Java 的開發人員和性能測試人員介紹一些 AIX 平臺上調查 Java 內存問題的工具以及分析方法。

0 評論:

顏 曉超, 軟件工程師, IBM

顧 彬, 軟件工程師, IBM

2012 年 8 月 09 日

  • 內容

在 IBM Bluemix 雲平臺上開發並部署您的下一個應用。

現在就開始免費試用

引言

Java 開發者一般不需要考慮內存釋放問題,全交由 GC 去處理。但是在一些生產環境中,JVM 經過長時間運行後,即使是一些很小的未釋放的 Java 對象,日積月累也會導致內存資源枯竭,最終使 Java 應用崩潰的問題。本文將就一個 AIX 平臺上基於 IBM JDK 開發的 Java 應用內存枯竭的實際案例分析過程,來引領讀者理解基於 IBM JDK 的 Java 應用內存泄漏調查方法,以及分析思路。

回頁首

第一步,判斷是否是內存泄漏問題

根據生產環境出現的錯誤日誌以及 GC 日誌文件,進行初步判斷是否是內存泄露問題。

Java 應用的錯誤日誌:

“***WARNING*** Java heap is almost exhausted: 4% free Java heap

應用程序中對可用內存做了判斷,當可用內存比較低的時候輸出了 WARNING 的日誌。

使用 IBM pattern modeling and Analysis Tools for Java Garbage Collector 來分析 GC 日誌。

圖 1. 選擇打開 IBM JDK 的 GC 日誌文件
圖 2. 點擊 Graph View Part 顯示
圖 3. 顯示 GC 分析圖

從圖中可以看出 Java 內存的堆 (Heap) 的使用情況是持續的上升趨勢。

由此我們可以得出結論,Java 應用程序存在內存泄漏問題,導致內存堆得不到釋放。

回頁首

第二步,截取 Java 內存堆的轉存儲文件

在得出是內存堆泄漏的問題結論後,接下來就需要取得內存堆的轉存儲文件來做進一步分析。

在 AIX 平臺上截取 IBM JDK 的內存堆的轉存儲文件前,需要先對 IBM JDK 的 JVM 參數進行設置。有 2 種設置方式:

  1. 設置 IBM JDK 的全局變量:
     export IBM_HEAPDUMP=true
  2. 添加 JVM 啓動參數:

    -Xdump:system+heap+java:events=user,request=exclusive+prepwalk+compact

    設定完後需要重啓 JVM, 使設定生效。然後可以在 kill -QUIT pid 命令來生成轉存儲文件 (Dump),pid 爲實際啓動的 JVM 進程 ID。

    當內存泄漏情況非常小且緩慢的時候,無法從 1 個或 2 個轉存儲文件中分析出導致泄漏的 Java 對象。根據上面 GC 的日誌趨勢,制定如下的轉存儲文件的截取的方案。

    1. 截取週期爲 1 星期以上,每天一次。
    2. 每天固定時間截取,且避開發生大的 GC 的時間段。

    這樣可以得到幾個可以用來比對分析的轉存儲文件,以及避免正在運行中得一些 Java 對象對於分析的干擾。

回頁首

第三步,分析轉存儲文件

使用 MAT (Memory Analyzer Tool) 工具來分析轉存儲文件。由於實際轉存儲文件非常大,需要調整 MAT 工具的啓動參數文件(MemoryAnalyzer.ini),32 位的 window 平臺的話,最大也只能設定到 1.5G。因此當分析超大的轉存儲文件時,建議在 64 位 window 平臺上做,這樣可以分配更多的內存給 MAT 工具使用。

1)查找可疑泄漏點

在 MAT 的 Overview 中,可以點擊”Leak Suspect”來生成 Leak Suspect Reports, 做最直觀的分析。

圖 4. 點擊 Leak Suspect
圖 5. 顯示某 1 天的轉存儲文件分析結果。

如果連續幾天的轉存儲文件中,都是這個 Suspect 實例 (Instance) 的所佔比例最大,且所佔內存空間也在不斷上升,沒有下降的趨勢的話,那基本上可以斷定該實例是發生泄漏的對象了。

點擊打開該 Suspect 的 Detail 信息。

圖 6. 點擊 Details 鏈接

通過比對連續幾天的轉存儲文件,可以發現是 Hashtable 中得 Entry 對象的佔用空間不斷變大。

圖 7. 顯示 Detail 信息

那接下來進一步深入分析,到底在 Hashtable 中佔用空間增大到底是什麼實例。

2)深入分析

點擊 Suspect 實例,打開該實例的 Dominator Tree。

圖 8. 選擇 Dominator Tree 選項

可以在 Dominator Tree 中看到 Hashtable 中放的 Java Instance,依次爲

Company[] -> Event[] -> Task (Manager, Handler, xxxxx)

圖 9. 顯示 Dominator Tree 信息

分析其中 1 個複雜的 Task,點擊 Path to GC Roots 繼續深入分析 Task 的引用關係。Weak 和 Soft 引用會在 Major GC 是被釋放,所以查看下不包含他們的引用關係。

圖 10. 顯示可疑點的引用關係圖

根據 Java 應用的代碼調查,Company 和 Event 是常駐於 Service 靜態實例中。

引用 A 代碼分析

引用 A 的順序 Task <- Thread <- Record.Hashtable。Record 中得 Hashtable 中有對一個 Thread 的引用是比較奇怪的。因爲那將導致這個 Thread 的實例沒法釋放,從而導致 Task 的實例沒釋放。查看 Java 應用代碼發現,Thread 的實例被放入 Record 實例的靜態 Hashtable 中,但是沒有調用 Remove。

清單 1
 public class XXXXXX extends XXXXXBase 
 { 
  // …
   private static Hashtable currentXXXXXXX = new Hashtable(); 
  // …
   public void process (xxxx){ 
   // …
   currentXXXXXX.put(Thread.currentThread(), XXXX_); 
   // …
  }

引用 B 代碼分析

和引用 A 相似,Thread 被放入了 Factory 的靜態實例的 Hashtable 中,而且沒有 Remove。

引用 C 代碼分析

Task 是經由 Event 每次新建實例來啓動執行,當執行完後應當銷燬該 Task 的實例,不應長期存在於內存中。上圖的應用分析顯示 Event 中引用了 Task 的實例,因此 Task 沒法釋放。查看 Event 的代碼證明了確認如此,沒有將新建的 Task 實例重設爲 Null。

圖 11 引用分析結構圖

直接用 OQL(Object Query Language) 來查詢該 Task 實例,可以看到該 Task 的實例隨着時間不但增多。

圖 12. OQL 查詢結果

綜上所述,由於強引用的關係存在於靜態實例中,所以 Task 的實例沒法釋放,最終導致了內存枯竭。Java 內存堆泄漏的問題,多發生在靜態 Hashtable、Hashmap、Vector 的使用不當,還有諸如打開文件後沒有關閉,DB 和 Socket 連接打開沒有關閉之類的都會導致 GC 無法釋放引用的 Java 實例。

本文中所描述的通過 Java 內存堆和 GC 日誌來分析內存泄漏方法,以及 Eclipse MAT 和 IBM Pattern Modeling and Analysis Tool for Java Garbage Collector 工具適用於調查任何平臺上的 Java 應用程序。但文中提及的截取 Java 內存堆的轉存儲文件方法只限於在 AIX 平臺上的 IBM JDK。針對 Linux, Window 等平臺,或 Sun JDK 等有專門的截取方法,不在本文中一一描述。

回頁首

結束語

本文通過對一個實際內存泄漏的分析,以及一些實際使用中的工具和經驗技巧的介紹,展示裏分析 Java 內存分析的常規方法。


發佈了48 篇原創文章 · 獲贊 27 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章