HeapAnalyzer 是 IBM 的一個(gè)用來分析 Java 程序的內(nèi)存堆使用情況的圖形化工具。
IBM HeapAnalyzer怎么用?
IBM HeapAnalyzer,下載之后首先閱讀一下readme,這上面詳細(xì)寫了HeapAnalyzer的使用方法?梢栽诿钚兄休斎
打開heapdump文件后,我一般點(diǎn)擊“Analysis”里的“Tree View”,以樹的形式從根節(jié)點(diǎn)展示內(nèi)存對象分配的信息
第一行java.lang.ref.Refenrence這個(gè)class及它的76個(gè)children占用了67%的已用堆大小(31M/46M),它本身僅占用了76bits。雙擊java.lang.ref.Refenrence,我們可以看到它所引用的兩個(gè)子節(jié)點(diǎn)。其中一個(gè)子節(jié)點(diǎn)java.lang.ref.Finalizer后的67%指引我們內(nèi)存泄漏的問題應(yīng)該在它的引用上。
接下去你可以逐級(jí)展開,或者右鍵點(diǎn)擊“Locate a leak suspect”,讓HeapAnalyzer幫你找到泄漏可能發(fā)生的地方。泄漏一般發(fā)生在那些擁有“超乎尋常多”的引用(子節(jié)點(diǎn))的class上,正是這些創(chuàng)建后沒有釋放、累積了成千上百的對象,造成了OutOfMemory。右鍵中的“Go to the largest drop subtrees”也是以此為原理而設(shè)的,它的解釋為:
“Search for total size drop” will find a size drop between the total size of a parent and the biggest total size of child of the parent.
因?yàn)槌霈F(xiàn)泄漏的點(diǎn),每個(gè)子節(jié)點(diǎn)占用的內(nèi)存空間不大,但是巨大的數(shù)量會(huì)導(dǎo)致父節(jié)點(diǎn)占用的total size很大。不過反過來尋找到的點(diǎn)都是泄漏發(fā)生的地方這種說法是不成立的,否則也不需要我們來分析了。
如何分析 java heap dump 文件?
如果啟動(dòng)過程中發(fā)現(xiàn)控制臺(tái)有java.lang.OutOfMemoryError出現(xiàn),可以適當(dāng)加大上面的數(shù)字(800),給予更多的空間。
然后“Open”產(chǎn)生的dump文件,打開畫面如下,文件很大的話需要等待一段時(shí)間
ibm heapAnalyzer工具在打開時(shí)已經(jīng)進(jìn)行了基本的分析,上面全部完成后,會(huì)出現(xiàn)如下結(jié)果:
[-photo didnot display-]
除了顯示該要結(jié)果外,還生成了一棵樹。這個(gè)畫面先不要關(guān),直到你不再需要這個(gè)dump了。
基本術(shù)語:
[-photo didnot display-]
然后對上面的界面做一下簡單的介紹。
[-photo didnot display-]
每個(gè)節(jié)點(diǎn)樹的大小占總的堆棧大小,如94%,然后是這個(gè)類的在內(nèi)存中的大小,后面5個(gè)子對象,注意這個(gè)子對象的意思不是繼承關(guān)系中的子類,而是上面定義的:如果A對象參考B對象,則B對象是A對象的字對象。
然后該工具根據(jù)分析結(jié)果把可能產(chǎn)生泄漏的對象顯示了出來。如下圖:
[-photo didnot display-]
分析根據(jù)主要是child object和parent object的大小差別程度,如果子對象不大,而父對象超級(jí)大,很可能是因?yàn)楦笇ο笫且粋(gè)集合類(如數(shù)組),包含了大量子對象作為元素。
工具欄:
[-photo didnot display-]
點(diǎn)擊分析工具欄的表格圖標(biāo),顯示出下面的統(tǒng)計(jì)表格,可以點(diǎn)擊欄標(biāo)題進(jìn)行排序。各標(biāo)題意思簡單介紹如下:
TotalSize:這個(gè)對象,以及這個(gè)對象的所有子對象(以及子對象的子對象,也就是從這個(gè)對象可以參考到的所有對象)的大小的總和,單位為bits;
Size:這個(gè)對象的大小,如第一個(gè)56bits = 56/8bytes = 7b;
No.Child:子對象的個(gè)數(shù),不包括子對象的子對象;
No.Parent:父對象的個(gè)數(shù),不包括父對象的對象;
Name:對象的名稱。
Address:對象在heap中的地址。
1.分析結(jié)果
1.1大量的以java/util/HashMap$Entry為元素的數(shù)組,占據(jù)了總堆棧的8%,很高的比例。
1.2大量的java/util/Hashtable$HashtableEntry為元素的數(shù)組,占據(jù)總堆棧的5%。
1.3里面的數(shù)組大量指向java/util/Hashtable$HashtableCacheHashEntry對象。
根據(jù)分析,最有嫌疑的對象應(yīng)該是java/util/HashMap$Entry。
2.其他經(jīng)驗(yàn)收集:
“Heapdump工具的使用很簡單,難點(diǎn)在于找到“內(nèi)存泄漏的真正原因”,一般需要通過多個(gè)heapdump文件的對比才能找到!
“ObjectInputStream/ObjectOutputStream要注意內(nèi)存泄漏.reset()”
“因?yàn)镴DK的問題,如果使用的是:J2RE 5.0 IBM J9 2.3AIX ppc-32 build j9vmap3223-20070201,這個(gè)SR4的版本有個(gè)問題就是,限定了類加載器可加載的類數(shù)量,默認(rèn)為8192,如果超過此限制,就會(huì)拋出 OutOfMemory的錯(cuò)誤!
對于這個(gè)問題,可以設(shè)置增加類加載器可加載的類數(shù)量解決。
3.知識(shí)補(bǔ)充介紹
3.1堆(Heap)和非堆(Non-heap)內(nèi)存
按照官方的說法:“Java虛擬機(jī)具有一個(gè)堆,堆是運(yùn)行時(shí)數(shù)據(jù)區(qū)域,所有類實(shí)例和數(shù)組的內(nèi)存均從此處分配。堆是在Java虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建的!薄霸贘VM中堆之外的內(nèi)存稱為非堆內(nèi)存(Non-heap memory)”?梢钥闯鯦VM主要管理兩種類型的內(nèi)存:堆和非堆。簡單來說堆就是Java代碼可及的內(nèi)存,是留給開發(fā)人員使用的;非堆就是JVM留給自己用的,所以方法區(qū)、JVM內(nèi)部處理或優(yōu)化所需的內(nèi)存(如JIT編譯后的代碼緩存)、每個(gè)類結(jié)構(gòu)(如運(yùn)行時(shí)常數(shù)池、字段和方法數(shù)據(jù))以及方法和構(gòu)造方法的代碼都在非堆內(nèi)存中。
3.2堆內(nèi)存分配
JVM初始分配的內(nèi)存由-Xms指定,默認(rèn)是物理內(nèi)存的1/64;JVM最大分配的內(nèi)存由-Xmx指定,默認(rèn)是物理內(nèi)存的1/4。默認(rèn)空余堆內(nèi)存小于40%時(shí),JVM就會(huì)增大堆直到-Xmx的最大限制;空余堆內(nèi)存大于70%時(shí),JVM會(huì)減少堆直到-Xms的最小限制。因此服務(wù)器一般設(shè)置-Xms、-Xmx相等以避免在每次GC后調(diào)整堆的大小。
33非堆內(nèi)存分配
JVM使用-XX:PermSize設(shè)置非堆內(nèi)存初始值,默認(rèn)是物理內(nèi)存的1/64;由XX:MaxPermSize設(shè)置最大非堆內(nèi)存的大小,默認(rèn)是物理內(nèi)存的1/4。
3.4JVM內(nèi)存限制(最大值)
首先JVM內(nèi)存限制于實(shí)際的最大物理內(nèi)存,假設(shè)物理內(nèi)存無限大的話,JVM內(nèi)存的最大值跟操作系統(tǒng)有很大的關(guān)系。簡單的說就32位處理器雖然可控內(nèi)存空間有4GB,但是具體的操作系統(tǒng)會(huì)給一個(gè)限制,這個(gè)限制一般是2GB-3GB(一般來說Windows系統(tǒng)下為1.5G -2G,Linux系統(tǒng)下為2G -3G),而64bit以上的處理器就不會(huì)有限制了。