您好,登錄后才能下訂單哦!
java中如何使用mat分析java堆,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
MAT
是 memory analyzer
的簡稱,它是一款功能強大的java堆內存分析器,可以用來查找內存泄露,以及查看內存消耗的情況,可以在MAT官網進行下載。
jmap
、jconsole
、jvisualvm
等工具可以導出java應用程序的堆快照文件,MAT
也有該功能,如圖所示:
點擊“Accquire Heap Dump”菜單后,會彈出當前java應用程序列表,選擇要分析的應用程序即可,如圖所示:
除了直接在MAT
中導出應用程序的堆快照外,也可以通過“Open Heap Dump”來打開一個已有的堆快照文件。
如圖所示,顯示了正常打開堆快照文件后MAT
的界面:
在右側界面中,顯示了堆快照文件的大小、類、實例和ClassLoader的總數。
在餅圖中,顯示了當前堆快照中最大的對象。
將鼠標懸停在餅圖中,可以在左側的Inspector界面中,查看該對象的相應信息。
在餅圖中,單擊某對象,可以對選中的對象進行更多操作。
如圖所示,在工具欄上單擊柱狀圖,可以顯示系統中所有類的內存使用情況:
MAT
也可以查看java線程,如圖所示:
當然,這里查看java層面的應用線程,虛擬機的系統線程是無法顯示的。通過線程的堆棧,還可以查看局部變量的信息。如下圖所示,帶有 <local>
標記的為當前幀棧的局部變量,這部分信息可能存在缺失。
除此之外,MAT
也可以查看詳細的線程堆棧信息:
由此打開thread_detail
標簽頁,可以清晰地看到線程堆棧信息:
MAT
的另外一個常用功能,是在各個對象的引用列表中交叉查看。對于一個給定對象,通過MAT
可以找到引用當前對象的對象,即入引用(Incomming References
),以及當前對象引用的對象,即出引用(Outgoing References
),如圖所示:
淺堆(Shallow Heap)和深堆(Retained Heap)是兩個非常重要的概念,它們分別表示一個對象結構所占用的內存大小和一個對象被GC回收后,可以真實釋放的內存大小。
淺堆是指一個對象所消耗的內存。在32位系統中,一個對象引用會占據4字節,一個int類型變量會占據4字節,一個long類型變量會占8字節,每個對象頭需要占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所持有的對象的集合。深堆是指對象的保留集中所有的對象的淺堆大小之和。
注:淺堆指對象本身占用的內存,不包括其內部引用對象的大小。一個對象的深堆指只能通過該對象訪問到的(直接或間接)所有對象的淺堆之和,即對象被回收后,可以釋放的真實空間。
另外一個常用的概念是對象的實際大小。這里,對象的實際大小定義為一個對象所能觸及的所有對象的淺堆大小之和,也就是通常意義上我們說的對象大小。與深堆相比,似乎這個在日常開發中更為直觀和被人接受,但實際上,這個概念和垃圾回收無關。
如圖所示,顯示了一個簡單的對象引用關系圖,對象A引用了C和D,對象B引用了C和E。那么對象A的淺堆大小只是A本身,不含C和D,而A的實際大小為A、C、D三者之和。而A的深堆大小為A與D之和,由于對象C還可以通過對象B訪問到,因此不在對象A的深堆范圍內。
MAT
提供了一個稱為支配樹(Dominator Tree
)的對象圖。支配樹體現了對象實例間的支配關系。在對象引用圖中,所有指向對象B的路徑都經過對象A,則認為對象A支配對象B.如果對象A是離對象B最近的一個支配對象,則認為對象A為對象B的直接支配者。支配樹是基于對象間的引用圖所建立的,它有以下基本性質:
對象A的子樹(所有被對象A支配的對象集合)表示對象A的保留集(Reatined Set),即深堆。
如果對象A支配對象B,那么對象A的直接支配者也支配對象B.
支配樹的邊與對象引用圖的邊不直接對應。
如圖所示,左圖表示對象引用圖,右圖表示左圖所對應的支配樹。對象A和B由根對象直接支配,由于在到對象C的路徑中,可以經過A,也可以經過B,因此對象C的直接支配者也是根對象。對象F與對象D相互引用,因為到對象F的所有路徑必然經過對象D,因此,對象D是對象F的直接支配者。而到對象D的所有路徑中,必然經過對象C,即使是從對象F到對象D的引用,從根節點出發,也是經過對象C的,所以,對象D的直接支配者為對象C。
在MAT中,單擊工具欄上的對象支配樹按鈕,可以打開對象支配樹視圖:
注:在對象支配樹中,某個對象的子樹表示在該對象被回收后也將被回收的對象的集合。
MAT
堆分析案例解析首先準備案例代碼:
package jvm.chapter07; import java.util.List; import java.util.Vector; /** * {這里添加描述} * * @author chengyan * @date 2019-11-15 11:01 下午 */ public class Demo04 { private static List<WebPage> webPages = new Vector<>(); public static void createWebPages() { for(int i = 0; i < 100; i++) { WebPage webPage = new WebPage(); webPage.setUrl("http:www." + Integer.toString(i) + ".com"); webPage.setContent(Integer.toString(i)); webPages.add(webPage); } } public static void main(String[] args) { createWebPages(); Student student3 = new Student(3, "billy"); Student student5 = new Student(5, "alice"); Student student7 = new Student(7, "taotao"); for(int i = 0; i < webPages.size(); i++) { if(i % student3.getId() == 0) { student3.visit(webPages.get(i)); } if(i % student5.getId() == 0) { student5.visit(webPages.get(i)); } if(i % student7.getId() == 0) { student7.visit(webPages.get(i)); } } webPages.clear(); System.gc(); } } class Student { private int id; private String name; private List<WebPage> history = new Vector<>(); public void visit(WebPage webPage) { history.add(webPage); } public Student(int id, String name) { this.id = id; this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<WebPage> getHistory() { return history; } public void setHistory(List<WebPage> history) { this.history = history; } } class WebPage { private String url; private String content; public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } }
可以看到,在 Demo04.java
類中,首先創建了100個網址,為了閱讀方便,這里的網址均以數字作為域名,分別為0~99.之后,程序創建了3名學生:billy、alice和taotao.他們分別瀏覽了能被3、5、7整除的網址。在程序運行后,3名學生的history中應該保存他們各自訪問過的網址。現在,希望在程序退出前,得到系統的堆信息并加以分析,查看每個學生實際訪問的網址。
使用如下參數運行程序:
-XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=./stu.hprof
MAT
打開堆文件使用MAT
打開產生的stu.hprof文件,如圖所示:
右側界面中,顯示了堆快照文件的大小、類、實例和ClassLoader的總數。在右側的餅圖中,顯示了當前堆快照中最大的對象。將鼠標懸停在餅圖中,可以在左側的Inspector界面中,查看該對象的相應信息。在餅圖中單擊某對象,可以對選中的對象進行更多的操作。
在線程視圖中可以通過main線程
找到3名學生的引用,如圖所示,可以清晰看到三個對象的學生名。除了對象名稱,MAT
還給出了淺堆大小和深堆大小。可以看到,所有的Student類的淺堆統一為24字節,和他們持有的內容無關。而深堆大小各不相同,這和每名學生訪問的網址有關。
當然,這里查看Java層面的應用線程,對于虛擬機的系統線程是無法顯示的。通過線程的堆棧,還可以查看局部變量的信息。帶有 <local>
標記的,就為當前幀棧的局部變量,這部分信息可能存在缺失。
為了獲得taotao同學訪問過的網址,可以在taotao的記錄中通過“出引用”(Outgoing References
)查找,可以找到由taotao可以觸及的對象,也就是他們訪問過的網址,如圖所示:
訪問過的網址如下:
可以看到堆中完整顯示了所有taobao同學的history中的網址。
如果現在希望查看哪些同學訪問了“http://www.0.com”,則可以在對應的WebPage對象中通過“入引用”(Incoming References
)查找:
顯然,這個網址被3名學生都訪問過了。
如圖所示,顯示了Main Thread
的對象支配樹。被Student
對象直接支配的對象,均放在該對象下的history
中,即當Student
對象被回收時,也會一并回收該對象所支配的所有對象。
另外,還有一部分WebPage
的父節點是Thread
,這表明這部分的WebPage
同時被多個Student
對象持有,例如,84能同時被3和7整除,這表明該對象會同時被billy
與taobao
持有,當billy
或taobao
其中之一并回收時,該對象并不會回收,因此不會單獨顯示在billy
或taobao
的history
中。
在工具欄上單擊柱狀圖,可以顯示系統中所有類的內存使用情況。圖為系統內所有類的統計信息,包含類的實例數量和占用的空間。
為了方便查看,柱狀圖還提供了根據Class Loader和包對類進行排序。如下圖是按照包排序的柱狀圖輸出。
看完上述內容,你們掌握java中如何使用mat分析java堆的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。