相信很多初學java的同學都有這種感覺:jvm為java開發者節省了很多內存相關的思考,我們不需要分配內存和手動釋放對象回收內存,堆內存與開發者的距離變得太遠了。其結果就是,不太講究的內存使用使得java開發者在面對高內存或者線上環境棧溢出時容易一頭霧水。
如果你也曾經想過扒開jvm的底褲看看到底是什麼對象填滿了整個堆內存,那麼絕不應該錯過Jprofiler。這是一個非常好用的可視化jvm監視工具,可以連接運行中的jvm,也可以讀取線上OOM時的dump文件,從而可視化、直觀地快速定位到問題。
適用範圍
適用情況:
1.數量較少的對象、單例或由單例持有的對象
這種類型的對象能夠以類型為依據快速定位到,從而易於找到異常對象。如果是單例或單例的屬性則更加容易發現。
2.業務與類型強相關的,比如某個類的對象只在某個業務中使用
3.問題發生在類的靜態屬性中
類對象(Class對象)在同一個類加載器家族中也是單例的,所以也容易定位。但是如果是一個tomcat服務器中加載大量war包的情況,可能不太容易發現。
不適用情況:
1.有大量字節流處理的業務
這種情況下堆內存中往往有大量的byte[]對象,容易干擾判斷
2.類的適用範圍太廣
同樣是不太容易定位到具體產生問題的對象屬於哪個業務
3.對性能要求高、沒法dump的業務
連接JProfiler後程序性能會下降很多。不過都已經發生內存泄漏了,也許內存才是當前最關注的問題。
示例程序
import java.util.HashMap;
public class FindObjectsInHeap {
public static void main(String[] args) {
MyObject myTestBean = new MyObject(new HashMap<>());
while (true) {
myTestBean.doMyThing();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static class MyObject {
private HashMap<Integer, Integer> myMap;
private int myNum = 0;
public MyObject(HashMap<Integer, Integer> map) {
this.myMap = map;
}
public void doMyThing() {
myMap.put(myNum, ++myNum);
}
}
}
用上述程序運行時,程序會持續地向myTestBean中的map添加entry,直至內存溢出。可以通過Jprofiler觀察到該全過程。
使用步驟
1.使用JProfiler連接運行中的JVM
2.使用JProfiler的HeapWalker創建一個堆內存的SnapShot
3.在對象列表中找到想要查看的對象
對於單例、數量較少的對象,比較容易找到
比較適合的是被SpringBoot容器管理起來的對象
4.雙擊對象 選擇Reference為Outgoing references,這樣可以查看指定對象持有的成員變量(Outgoing references表示查看指定對象引用了哪些對象,Incoming references表示查看指定對象被哪些對象引用)
5.使用腳本對對象進行查看
假設想要查看myMap中某個key的value
展開想要查看的對象後發現對象很大,沒辦法手動查看或者篩選
選中map後點擊圖中按鈕 打開腳本編輯器
腳本編輯器可以將選中的對象作為參數傳入。使用對象的一些成員方法來操作對象,篩選結果
if(hashMap.containsKey(65535)){
return "it exists.";
}else{
return "not exist.";
}
點擊ok來執行腳本
通過這種方式便可簡便地訪問內存中的對象了。
雖然通過debug打斷點的方式也能通過計算表達式對暫停中的堆內存對象進行篩選和遍歷,但會中斷線上業務,不太方便 使用JProfiler比較安全.
總之,在我的職業生涯初期,jProfiler在幫助直觀理解內存、線程模型上起了很大作用,也幫我解決過一些實際的內存泄露問題。雖然在使用上稍顯麻煩,但直觀的UI界面對新手會有很大幫助。如果你也遇到了類似問題,希望這篇文章能幫到你。