https://www.cnblogs.com/yueshutong/p/9824772.html
MAT記憶體分析工具
MAT是Memory Analyzer的簡稱,它是一款功能強大的Java堆記憶體分析器。可以用于查找記憶體洩露以及檢視記憶體消耗情況。MAT是基于Eclipse開發的,是一款免費的性能分析工具。讀者可以在http://www.eclipse.org/mat/下載下傳并使用MAT。
1 初識MAT
在分析堆快照前,首先需要導出應用程式的堆快照。在本書前文中提到的jmap、JConsole和Visual VM等工具都可用于獲得Java應用程式的堆快照檔案。此外,MAT本身也具有這個功能。
單擊左上角的“File”菜單下的“Accquire Heap Dump”選項後,會彈出目前Java應用程式清單,選擇要分析的應用程式即可,如圖所示。
除了直接在MAT中導出正在運作的應用程式堆快照外,也可以通過“Open Heap Dump”來打開一個既存的堆快照檔案。
注意:使用MAT既可以打開一個已有的堆快照,也可以通過MAT直接從活動Java程式中導出堆快照。
如圖所示,顯示了正常打開堆快照檔案後的MAT的界面。
右側界面中,顯示了堆快照檔案的大小、類、執行個體和ClassLoader的總數。在右側的餅圖中,顯示了目前堆快照中最大的對象。将滑鼠懸停在餅圖中,可以在左側的Inspector界面中,檢視該對象的相應資訊。在餅圖中單擊某對象,可以對選中的對象進行更多的操作。
在工具欄上單擊柱狀圖,可以顯示系統中所有類的記憶體使用情況。圖為系統内所有類的統計資訊,包含類的執行個體數量和占用的空間。
另外一個實用的功能是,可以通過MAT檢視系統中的Java線程,如圖所示。
當然,這裡檢視Java層面的應用線程,對于虛拟機的系統線程是無法顯示的。通過線程的堆棧,還可以檢視局部變量的資訊。如上圖所示,帶有“<local>”标記的,就為目前幀棧的局部變量,這部分資訊可能存在缺失。
MAT的另外一個常用功能,是在各個對象的引用清單中穿梭檢視。對于給定一個對象,通過MAT可以找到引用目前對象的對象,即入引用(Incomming References),以及目前對象引用的對象,即出引用(Outgoing References),如圖7.11所示。
下圖顯示了with outgoing reference 的輸出。
為了友善檢視,柱狀圖還提供了根據Class Loader和包對類進行排序。如下圖是按照包排序的柱狀圖輸出。
2 淺堆和深堆
淺堆(Shallow Heap)和深堆(Retained Heap)是兩個非常重要的概念,它們分别表示一個對象結構所占用的記憶體大小和一個對象被GC回收後,可以真實釋放的記憶體大小。
淺堆(Shallow Heap)是指一個對象所消耗的記憶體。在32位系統中,一個對象引用會占據4個位元組,一個int類型會占據4個位元組,long型變量會占據8個位元組,每個對象頭需要占用8個位元組。
根據堆快照格式不同,對象的大小可能會向8位元組進行對齊。以String對象為例,如下圖所示,顯示了String對象的幾個屬性。
- String
- value:char[]
- offset:int
- count:int
- hash:int
3個int值共占12位元組,對象引用占用4位元組,對象頭8位元組,合計24位元組。淺堆的大小隻與對象的結構有關,與對象的實際内容無關。也就是說,無論字元串的長度有多少,内容是什麼,淺堆的大小始終是24位元組。
深堆(Retained Heap)的概念略微複雜。要了解深堆,首先需要了解保留集(Retained Set)。對象A的保留集指當對象A被垃圾回收後,可以被釋放的所有的對象集合(包括對象A本身),即對象A的保留集可以被認為是隻能通過對象A被直接或間接通路到的所有對象的集合。通俗地說,就是指僅被對象A所持有的對象的集合。深堆是指對象的保留集中所有的對象的淺堆大小之和。
注意:淺堆指對象本身占用的記憶體,不包括其内部引用對象的大小。一個對象的深堆指隻能通過該對象通路到的(直接或間接)所有對象的淺堆之和,即對象被回收後,可以釋放的真實空間。
另外一個常用的概念是對象的實際大小。這裡,對象的實際大小定義為一個對象所能觸及的所有對象的淺堆大小之和,也就是通常意義上我們說的對象大小。與深堆相比,似乎這個在日常開發中更為直覺和被人接受,但實際上,這個概念和垃圾回收無關。
如圖7.14所示,顯示了一個簡單的對象引用關系圖,對象A引用了C和D,對象B引用了C和E。那麼對象A的淺堆大小隻是A本身,不含C和D,而A的實際大小為A、C、D三者之和。而A的深堆大小為A與D之和,由于對象C還可以通過對象B通路到,是以不在對象A的深堆範圍内。
在MAT中檢視對象淺堆和深堆的大小:
選中對象,單擊右鍵,在彈出的菜單中都有 Show Retained Set 指令,它可用于顯示指定類或者對象的保留集。
3 支配樹(Dominator Tree)
MAT提供了一個稱為支配樹(Dominator Tree)的對象圖。支配樹展現了對象執行個體間的支配關系。在對象引用圖中,所有指向對象B的路徑都經過對象A,則認為對象A支配對象B。如果對象A是離對象B最近的一個支配對象,則認為對象A為對象B的直接支配者。支配樹是基于對象間的引用圖所建立的,它有以下基本性質:
- 對象A的子樹(所有被對象A支配的對象集合)表示對象A的保留集(retained set),即深堆。
- 如果對象A支配對象B,那麼對象A的直接支配者也支配對象B。
- 支配樹的邊與對象引用圖的邊不直接對應。
如圖7.19所示,左圖表示對象引用圖,右圖表示左圖所對應的支配樹。對象A和B由根對象直接支配,由于在到對象C的路徑中,可以經過A,也可以經過B,是以對象C的直接支配者也是根對象。對象F與對象D互相引用,因為到對象F的所有路徑必然經過對象D,是以,對象D是對象F的直接支配者。而到對象D的所有路徑中,必然經過對象C,即使是從對象F到對象D的引用,從根節點出發,也是經過對象C的,是以,對象D的直接支配者為對象C。
同理,對象E支配對象G。到達對象H的可以通過對象D,也可以通過對象E,是以對象D和E都不能支配對象H,而經過對象C既可以到達D也可以到達E,是以對象C為對象H的直接支配者。
在MAT中,單擊工具欄上的對象支配樹按鈕,可以打開對象支配樹視圖,如圖7.20所示。
注意:對象支配樹中,某一個對象的子樹,表示在該對象被回收後,也将被回收的對象的集合。
4 垃圾回收根
在Java系統中,作為垃圾回收的根節點可能是以下對象之一:
- 系統類:被 bootstrap/system ClassLoader加載的類。如在 rt.jar包中的所有類。
- JNI局部變量:本地代碼中的局部變量。如使用者自定義的JNI代碼或者JVM内部代碼。
- JNI全局變量:本地代碼中的全局變量。
- 線程:開始、并且沒有停止的線程。
- 正在使用的鎖:作為鎖的對象。比如,調用了 wait() 或者 notify() 方法的對象。或者調用了 synchronized(Object)操作的對象。
- Java局部變量:如函數的輸入參數以及方法中的局部變量。
- 本地棧:本地代碼中的輸入輸出參數。比如使用者自定義的JNI代碼或者JVM内部代碼。
- Finalizer:在等待隊列中将要被執行析構函數的對象。
- Unfinalized:擁有析構函數,但是沒有被析構,且不在析構隊列中的對象。
- 不可達對象:從任何一個根對象,都無法達到的對象。但為了能夠在MAT中分析,被MAT标志位根。
- 未知對象:未知的根類型。用于處理一些特殊的堆格式。
通過MAT,可以列出所有的根對象,如下圖所示。
5 記憶體洩漏檢測
MAT 提供了自動檢測記憶體洩漏,以及統計堆快照内對象分布情況的工具,如圖所示:
6 最大對象報告
系統中占有記憶體最大的幾個對象,往往是解決系統性能問題的關鍵所在。如果應用程式發生記憶體洩漏,那麼洩漏的對象通常會在堆快照中所占據很大的比重。是以,檢視和分析堆快照中最大的對象,具有較高的價值。
在MAT中,可以自動查找并顯示消耗記憶體最多的幾個對象,如圖所示,可以打開以餅圖和表格為形式的最大對象報告。
7 查找支配者
通過MAT,開發人員還可以很友善地查找某一個對象或者類的支配者。如下圖所示。
在參數對話框中,務必正确填寫 -skip 參數。查詢結果會忽略所有定義在 -skip 參數中的類和執行個體。
輸出結果是不滿足 -skip 所指定正規表達式的、所有選中對象或類的直接的支配者。
8 線程分析
9 集合使用情況分析
使用這些工具,可以檢視數組、集合的填充率;可以觀察集合内的資料;也可以分析哈希表的沖突率。
MAT對OQL的支援
MAT的OQL文法與Visual VM支援的OQL有着很大不同。MAT支援一種類似于SQL的查詢語言OQL(Object Query Language)。OQL使用類SQL文法,可以在堆中進行對象的查找和篩選。本節将主要介紹OQL的基本使用方法,幫助讀者盡快掌握這種堆檔案的檢視方式。
1 Select子句
在MAT中,Select子句的格式與SQL基本一緻,用于指定要顯示的列。Select子句中可以使用“*”,檢視結果對象的引用執行個體(相當于outgoing references)。
select * from java.util.ArrayList A
以上查詢的輸出如圖所示,在輸出結果中,結果集中的每條記錄都可以展開,檢視各自的引用對象。
OQL還可以指定對象的屬性進行輸出,下例輸出所有Vector對象的内部數組,輸出結果如圖7.31所示。使用“OBJECTS”關鍵字,可以将傳回結果集中的項以對象的形式顯示。
SELECT OBJECTS v.elementData FROM java.util.Vector v
下例顯示String對象的char數組(用于JDK 1.7的堆):
SELECT OBJECTS s.value FROM java.lang.String s
在Select子句中,使用“AS RETAINED SET”關鍵字可以得到所得對象的保留集。下例得到geym.zbase.ch7.heap.Student對象的保留集。
SELECT AS RETAINED SET * FROM geym.zbase.ch7.heap.Student
“DISTINCT”關鍵字用于在結果集中去除重複對象。下例的輸出結果中隻有一條“class java.lang.String”記錄。如果沒有“DISTINCT”,那麼查詢将為每個String執行個體輸出其對應的Class資訊。
SELECT DISTINCT OBJECTS classof(s) FROM java.lang.String s
2 From子句
From子句用于指定查詢範圍,它可以指定類名、正規表達式或者對象位址。
下例使用From子句,指定類名進行搜尋,并輸出所有的java.lang.String執行個體。
SELECT * FROM java.lang.String s
下例使用正規表達式,限定搜尋範圍,輸出所有java.lang包下所有類的執行個體,如圖所示。
SELECT * FROM "java\.lang\..*"
也可以直接使用類的位址進行搜尋。使用類的位址的好處是可以區分被不同ClassLoader加載的同一種類型。下例中“0x37a014d8”為類的位址。
select * from 0x37a014d8
有多種方法可以獲得類的位址,在MAT中,一種最為簡單的方法如圖所示。
在From子句中,還可以使用“INSTANCEOF”關鍵字,傳回指定類的所有子類執行個體。下例的查詢傳回了目前堆快照中所有的抽象集合執行個體,包括java.util.Vector、java.util.ArrayList和java.util.HashSet等。
SELECT * FROM INSTANCEOF java.util.AbstractCollection
在From子句中,還可以使用“OBJECTS”關鍵字。使用“OBJECTS”關鍵字後,那麼原本應該傳回類的執行個體的查詢,将傳回類的資訊。
SELECT * FROM OBJECTS java.lang.String
以上查詢的傳回結果如圖所示。它僅傳回一條記錄,表示java.lang.String的類的資訊。如果不使用“OBJECTS”關鍵字,這個查詢将傳回所有的java.lang.String執行個體。
“OBJECTS”關鍵字也支援與正規表達式一起使用。下面的查詢,傳回了所有滿足給定正規表達式的所有類,其結果如圖所示。
SELECT * FROM OBJECTS "cn\.zyzpp\..*"
注意:在From子句中使用OBJECTS關鍵字,将傳回符合條件的類資訊,而非執行個體資訊。這與Select子句中的OBJECTS關鍵字是完全不同的。
3 Where子句
Where子句用于指定OQL的查詢條件。OQL查詢将隻傳回滿足Where子句指定條件的對象。Where子句的格式與傳統SQL極為相似。
下例傳回長度大于10的char數組。
SELECT * FROM char[] s WHERE [email protected]>10
下例傳回包含“java”子字元串的所有字元串,使用“LIKE”操作符,“LIKE”操作符的操作參數為正規表達式。
SELECT * FROM java.lang.String s WHERE toString(s) LIKE ".*java.*"
下例傳回所有value域不為null的字元串,使用“=”操作符。
SELECT * FROM java.lang.String s where s.value!=null
Where子句支援多個條件的AND、OR運算。下例傳回數組長度大于15,并且深堆大于1000位元組的所有Vector對象。
SELECT * FROM java.util.Vector v WHERE [email protected]>15 AND [email protected]>1000
4 内置對象與方法
OQL中可以通路堆内對象的屬性,也可以通路堆内代理對象的屬性。通路堆内對象的屬性時,格式如下:
[ <alias>. ] <field> . <field>. <field>
其中alias為對象名稱。
下例通路java.io.File對象的path屬性,并進一步通路path的value屬性。
SELECT toString(f.path.value) FROM java.io.File f
以上查詢得到的結果如圖7.38所示。
這些堆内對象的屬性與Java對象一緻,擁有與Java對象相同的結果。
MAT為了能快捷地擷取堆内對象的額外屬性(比如對象占用的堆大小、對象位址等),為每種元類型的堆内對象建立了相對應的代理對象,以增強原有的對象功能。通路代理對象的屬性時,使用如下格式:
[ <alias>. ] @<attribute>
其中,alias為對象名稱,attribute為屬性名。
下例顯示了String對象的内容、objectid和objectAddress。
SELECT s.toString(), [email protected], [email protected] FROM java.lang.String s
下例顯示了File對象的對象ID、對象位址、代理對象的類型、類的類型、對象的淺堆大小以及對象的顯示名稱。
SELECT [email protected], [email protected], [email protected], [email protected], [email protected], [email protected] FROM java.io.File f
下例顯示java.util.Vector内部數組的長度。
SELECT [email protected] FROM java.util.Vector v
下表整理了MAT代理對象的基本屬性。
對象類型 | 接口 | 屬性 | 功能 |
---|---|---|---|
基對象 | IObejct | objectId | 對象ID |
objectAddress | 對象位址 | ||
class | 代理對象類型 | ||
clazz | 對象類類型 | ||
usedHeapSize | 淺堆大小 | ||
retainedHeapSize | 深堆大小 | ||
displayName | 顯示名稱 | ||
Class對象 | IClass | classLoaderId | ClassLoad的ID |
數組 | IArray | length | 數組長度 |
元類型數組 | IPrimitiveArray | valueArray | 數組内容 |
對象數組 | IObjectArray | referenceArray | 數組内容 |
除了使用代理對象的屬性,OQL中還可以使用代理對象的方法,使用格式如下:
[ <alias> . ] @<method>( [ <expression>, <expression> ] )
下例顯示int數組中索引下标為2的資料内容。
SELECT s.getValueAt(2) FROM int[] s WHERE ([email protected] > 2)
下例顯示對象數組中索引下标為2的對象。
SELECT OBJECTS [email protected](2) FROM java.lang.Object[] s WHERE ([email protected] > 2)
下例顯示了目前堆中所有的類型。
select * from ${snapshot}.getClasses()
下例顯示了所有的java.util.Vector對象及其子類型,它的輸出如圖所示。
select * from INSTANCEOF java.util.Vector
下例顯示目前對象是否是數組。
SELECT c, classof(c).isArrayType() FROM ${snapshot}.getClasses() c
代理對象的方法整理如表所示。
表 MAT代理對象的方法
對象說明 | 對象名 | 對象方法 | 對象方法說明 |
---|---|---|---|
全局快照 | ISnapshot | getClasses() | 所有執行個體的集合 |
getClassesByName(String name, boolean includeSubClasses) | 根據名稱選取符合條件的執行個體 | ||
類對象 | IClass | hasSuperClass() | 是否有超類 |
isArrayType() | 是否是數組 | ||
基對象 | IObject | getObjectAddress() | 取得對象位址 |
元類型數組 | IPrimitiveArray | getValueAt(int index) | 取得數組中給定索引的資料 |
元類型數組,對象數組 | [] or List | get(int index) | 取得數組中給定索引的資料 |
MAT的OQL中還内置一些有用的函數,如表所示。
表 OQL中的内置函數
函數 | 說明 |
---|---|
toHex( number ) | 轉為16進制 |
toString( object ) | 轉為字元串 |
dominators( object ) | 取得直接支配對象 |
outbounds( object ) | 取得給定對象引用的對象 |
inbounds( object ) | 取得引用給定對象的對象 |
續表
函數 | 說明 |
---|---|
classof( object ) | 取得目前對象的類 |
dominatorof( object ) | 取得給定對象的直接支配者 |
下例顯示所有長度為15的字元串内容(JDK 1.7導出的堆)。
SELECT toString(s) FROM java.lang.String s WHERE (([email protected] = 15) and (s.value != null))
下例顯示所有cn.zyzpp.jConsole.HProfTest對象的直接支配對象。即給定對象回收後,将釋放的對象集合。
SELECT objects dominators(s) FROM cn.zyzpp.jConsole.HProfTest s
以上查詢的輸出如圖所示,顯示HProfTest對象支配了1個字元串對象。
函數dominatorof()與dominators()的功能相反,它擷取直接支配目前對象的對象。
SELECT distinct objects dominatorof(s) FROM cn.zyzpp.jConsole.HProfTest s
以上查詢的輸出如圖所示,顯示所有的HProfTest對象直接被主線程支配。
注意:函數dominatorof()與dominators()的功能正好相反。dominatorof()用于獲得直接支配目前對象的對象,而dominators()用于擷取直接支配對象。
下例取得引用WebPage的對象。
SELECT objects inbounds(w) FROM geym.zbase.ch7.heap.WebPage w
下例取得堆快照中所有在cn.zyzpp包中的存在對象執行個體的類型,其輸出如圖所示。
SELECT distinct objects classof(obj) FROM "cn\.zyzpp\..*" obj
參考
《Java程式性能優化》葛一鳴