最近一段時間一直在研究熱部署,熱部署中涉及到一個比較頭痛的問題就是查內存泄露(Memory Leak),因而乎在研究熱部署的過程當中,乾的最多的一件事就是查內存泄露。
查內存泄露,最開始嘗試用JDK自身的工具去解決這件事,經過jstat和jmap,去發現是否有內存泄露,當判斷有內存泄露存在時,試圖要去尋找內存泄露的點時,發現單純使用JDK自身提供的工具沒有什麼很好的辦法,我嘗試過Jhat,發現查起來太困難了,後來對比網上推薦的工具,我選擇了MAT(Memory Analyzer Tool)。
MAT是一個eclipse的插件,上手起來比較快。它可以快速的分析dump文件,能夠直觀的看到各個對象在內存佔用的量大小,以及類實例的數量,對象之間的引用關係,找出對象的GC Roots相關的信息,此外還能生成內存泄露報表,疑似泄露大對象的報表等等。php
- 安裝MAT
- 能夠選擇eclipse插件的方式安裝
- http://download.eclipse.org/mat/1.3/update-site/
- 也能夠選擇單獨MAT程序下載安裝
- http://www.eclipse.org/mat/downloads.php
- 使用MAT查內存溢出
- 生成dump
- 生成dump文件,能夠直接用 jmap -dump:format=b,file=xxx.bin ${pid}的方式
- 也能夠直接用MAT生成,File-》Acquire Heap Dump -》選擇要dump的java進程-》finish就能夠了
- 生成完dump後,能夠用MAT打開 dump(若是是MAT dump完後會自動進行解析),File-》Open Heap Dump 對dump文件進行解析,最終生成一個Overview視圖,這個圖是一個概要圖,顯示了一些統計信息,包括整個size大小,class數量,以及對象 的數量,同時還將生成一個大對象的top圖,併線顯示大對象佔用內存的百分比。
- 相似:size:2.2MB Classes:3.3k Objects:50.1k ClassLoader:84 Unreachable Objects Histogram
- 找出溢出源
- Histogram視圖(截圖裏柱子那個,邊上的是Dominator Tree ):列出每個class產生了多少個實例,以及佔有多大內存,所佔百分比
-
- 能夠很容易找出站內存最多的幾個類,根據Retained Heap排序,找出前幾個。
- 能夠分不一樣的維度來查看類的Histogram視圖,Group by class、Group by superclass、Group by class loader、Group by package
- 只要有溢出,時間久了,溢出類的實例數量或者其佔有的內存會愈來愈多,排名也就愈來愈前,經過屢次對比不一樣時間點下的Histogram圖對比就能很容易把溢出類找出來。
- Dominator Tree(支配樹):列出每一個對象(Object instance)與其引用關係的樹狀結構,還包含了佔有多大內存,所佔百分比
- 能夠很容易的找出佔用內存最多的幾個對象,根據Percentage(百分比)來排序。
- 能夠分不一樣維度來查看對象的Dominator Tree視圖,Group by class、Group by class loader、Group by package
- 和Histogram相似,時間久了,經過屢次對比也能夠把溢出對象找出來,Dominator Tree和Histogram的區別是站的角度不同,Histogram是站在類的角度上去看,Dominator Tree是站的對象實例的角度上看,Dominator Tree能夠更方便的看出其引用關係。
- 定位溢出的緣由
- 經過Path to GC Roots或者Merge Shortest Paths to GC Roots
- 通 過Histogram視圖或者Dominator Tree視圖,找到疑似溢出的對象或者類後,選擇Path to GC Roots或者Merge Shortest Paths to GC Roots,這裏有不少過濾選項,通常來說能夠選擇exclude all plantom/weak/soft etc. references。這樣就排除了虛引用、弱引用、以及軟引用,剩下的就是強引用。從GC上說,除了強引用外,其餘的引用在JVM須要的狀況下是均可以 被GC掉的,若是一個對象始終沒法被GC,就是由於強引用的存在,從而致使在GC的過程當中一直得不到回收,所以就內存溢出了。
- 接下來就須要直接定位具體的代碼,看看如何釋放這些不應存在的對象,好比是否被cache住了,仍是其餘什麼緣由。
- 找到緣由,清理乾淨後,再對照以前的操做,看看對象是否還再持續增加,若是不在,那就說明這個溢出點被成功的堵住了。
- 最後用jstat跟蹤一段時間,看看Old和Perm區的內存是否最終穩定在一個範圍內,若是長時間穩定在一個範圍,那溢出的問題就解決了,若是還再繼續增加,那繼續用上述方法,看看是否存在其餘代碼的溢出點,繼續找出,將其堵住。
- 此外經過list objects或show objects by class也能夠達到相似的效果,不過沒看GC Roots的方式直觀,這裏就不細說了。
- list objects -- with outgoing references : 查看這個對象持有的外部對象引用。
- list objects -- with incoming references : 查看這個對象被哪些外部對象引用。
- show objects by class -- with outgoing references :查看這個對象類型持有的外部對象引用
- show objects by class -- with incoming references :查看這個對象類型被哪些外部對象引用