Java內存泄漏分析系列之七:使用MAT的Histogram和Dominator Tree定位溢出源

基礎概念

先列出幾個基礎的概念:html

Shallow Heap 和 Retained Heap

Shallow Heap表示對象自己佔用內存的大小,不包含對其餘對象的引用,也就是對象頭加成員變量(不是成員變量的值)的總和。java

Retained Heap是該對象本身的Shallow Heap,並加上從該對象能直接或間接訪問到對象的Shallow Heap之和。換句話說,Retained Heap是該對象GC以後所能回收到內存的總和。正則表達式

把內存中的對象當作下圖中的節點,而且對象和對象之間互相引用。這裏有一個特殊的節點GC Roots,這就是reference chain的起點。
緩存

從obj1入手,上圖中藍色節點表明僅僅只有經過obj1才能直接或間接訪問的對象。由於能夠經過GC Roots訪問,因此左圖的obj3不是藍色節點;而在右圖倒是藍色,由於它已經被包含在retained集合內。因此對於左圖,obj1的retained size是obj一、obj二、obj4的shallow size總和;右圖的retained size是obj一、obj二、obj三、obj4的shallow size總和。obj2的retained size能夠經過相同的方式計算。工具

對象引用(Reference)

對象引用按從最強到最弱有以下級別,不一樣的引用(可到達性)級別反映了對象的生命週期:spa

  • 強引用(Strong Ref):一般咱們編寫的代碼都是強引用,於此相對應的是強可達性,只有去掉強可達性,對象才能被回收。
  • 軟引用(Soft Ref):對應軟可達性,只要有足夠的內存就一直保持對象,直到發現內存不足且沒有強引用的時候纔回收對象。
  • 弱引用(Weak Ref):比軟引用更弱,當發現不存在強引用的時候會當即回收此類型的對象,而不須要等到內存不足。經過java.lang.ref.WeakReference和java.util.WeakHashMap類實現。
  • 虛引用(Phantom Ref):根本不會在內存中保持該類型的對象,只能使用虛引用自己,通常用於在進入finalize()方法後進行特殊的清理過程,經過java.lang.ref.PhantomReference實現。

GC Roots和Reference Chain

JVM在進行GC的時候是經過使用可達性來判斷對象是否存活,經過GC Roots(GC根節點)的對象做爲起始點,從這些節點開始進行向下搜索,搜索所走過的路徑成爲Reference Chain(引用鏈),當一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來講就是從GC Roots到這個對象不可達)時,則證實此對象是不可用的。htm

以下圖所示,對象object 五、object 六、object 7雖然互相有關聯,它們的引用並不爲0,可是它們到GC Roots是不可達的,所以它們將會被斷定爲是可回收的對象。
對象

Histogram(直方圖)視圖

點擊工具欄上的 Histogram 圖標能夠打開Histogram(直方圖)視圖,能夠列出每一個類產生的實例數量,以及所佔用的內存大小和百分比。主界面以下圖所示:排序

圖中Shallow Heap 和 Retained Heap分別表示對象自身不包含引用的大小和對象自身幷包含引用的大小,具體請參考下面 Shallow Heap 和 Retained Heap 部分的內容。默認的大小單位是 Bytes,能夠在 Window - Preferences 菜單中設置單位,圖中設置的是KB。生命週期

經過直方圖視圖能夠很容易找到佔用內存最多的幾個類(經過Retained Heap排序),還能夠經過其餘方式進行分組(見下圖)。

若是存在內存溢出,時間久了溢出類的實例數量或者內存佔比會愈來愈多,排名也愈來愈靠前。能夠點擊工具類上的  圖標進行對比,經過屢次對比不一樣時間點下的直方圖對比就很容易把溢出的類找出來。

還有一種對比直方圖的方式,首先經過 Window 菜單打開 Navigation History 視圖,選中直方圖右鍵並選中 Add to Compare Basket項目,將直方圖添加到 Compare Basket 中。

而後在 Compare Basket 中點擊右上角的  按鈕,能夠分別列出對比的全部結果,見下圖:

而且在上面的能夠設置不一樣的對比方式。

Dominator Tree視圖

點擊工具欄上的  圖標能夠打開Dominator Tree(支配樹)視圖,在此視圖中列出了每一個對象(Object Instance)與其引用關係的樹狀結構,同時包含了佔用內存的大小和百分比。

經過Dominator Tree視圖能夠很容易的找出佔用內存最多的幾個對象(根據Retained Heap或Percentage排序),和Histogram相似,能夠經過不一樣的方式進行分組顯示:

定位溢出源

Histogram視圖和Dominator Tree視圖的角度不一樣,前者是基於類的角度,後者是基於對象實例的角度,而且能夠更方便的看出其引用關係。

首先,在兩個視圖中找出疑似溢出的對象或者類(能夠經過Retained Heap排序,而且能夠在Class Name中輸入正則表達式的關鍵詞只顯示指定的類名),而後右鍵選擇Path To GC Roots(Histogram中沒有此項)或Merge Shortest Paths to GC Roots,而後選擇 exclude all phantom/weak/soft etc. reference

GC Roots意爲GC根節點,其含義見上面的 GC Roots和Reference Chain 部分,後面的 exclude all phantom/weak/soft etc. reference 意思是排除虛引用、弱引用和軟引用,即只剩下強引用,由於除了強引用以外,其餘的引用均可以被JVM GC掉,若是一個對象始終沒法被GC,就說明有強引用存在,從而致使在GC的過程當中一直得不到回收,最終就內存溢出了。

經過結果就能夠很方便的定位到具體的代碼,而後分析是什麼緣由沒法釋放該對象,好比被緩存了或者沒有使用單例模式等等。

下面是執行的結果:

上圖中保留了大量的VelocitySqlBulder的外部引用,後來查看了代碼,原來每次調用的時候都實例化一個新的對象,因爲VelocitySqlBulder類是無狀態的工具類,所以修改成單例方式就能夠解決這個問題。

後續觀察

根據上面分析的結果對問題進行處理以後,再對照以前的操做,看看對象是否還再持續增加,若是沒有就說明這個地方的問題已經解決了。

最後再用 jstat 持續跟蹤一段時間,看看Old和Perm區的內存是否最終穩定在一個範圍以內,若是長時間穩定在一個範圍說明溢出問題獲得瞭解決,不然還要繼續進行分析和處理,一直到穩定爲止。

 

 

參考

 

原文連接:http://www.javatang.com

相關文章
相關標籤/搜索