標籤:jvisualvm 記憶體資訊
由於最近想自己動手測試一下String和StringBuffer的效率問題,需要擷取程式運行時的記憶體佔中資訊,於是上網查了一下,根據查到的資料寫了個程式,發現結果有問題,才發現查到的資料是錯誤的.所以在這裡跟大家分享一下擷取記憶體佔用的正確方法
錯誤的方法
//程式開始時:(先調用一下記憶體回收,但是不一定立即執行)Runtime.getRuntime().gc();long initm=Runtime.getRuntime().freeMemory();//程式結束時:Runtime.getRuntime().gc();long endm=Runtime.getRuntime().freeMemory();//計算空閑差:System.out.println(initm-endm);
關於
gc方法
首先,在最開始調用Runtime.getRuntime().gc()確實正確,但是注釋中說
先調用一下記憶體回收,但是不一定立即執行
這句話就有問題了,java api文檔中對gc方法的描述是這樣的:
調用 gc 方法暗示著 JAVA 虛擬機器做了一些努力來回收未用對象,以便能夠快速地重用這些對象當前佔用的記憶體。當控制權從方法調用中返回時,虛擬機器已經盡最大努力從所有丟棄的對象中回收了空間。
也就是說,調用gc時java虛擬機器一定會進行記憶體回收.那麼,為什麼網上甚至許多教科書上說gc方法並不一定會立即執行呢?個人猜測可能是運行完gc方法後,記憶體的消耗量可能並沒有顯著的減少,因為這個時候可能並沒有多少可以回收的垃圾.而過了一段時間後jvm又回收了一次記憶體,這次記憶體的消耗量明顯的減少了.於是好多地方都會說gc方法並不一定會立即執行.
而在第二次擷取”記憶體”時,是不能調用gc方法的,因為這樣做會把一些中間的臨時變數回收掉,導致擷取到的”記憶體”並不一定是運行過程中真正使用的”記憶體”量.
關於
freeMemory方法
那麼使用freeMemory方法擷取前後兩次的空閑記憶體,然後相減得到的不就應該是執行時消耗的記憶體嗎?
如果記憶體總量不變,那麼這種方法確實可以:
記憶體總量1 = 開始時空閑記憶體 + 開始時使用記憶體
記憶體總量2 = 結束時空閑記憶體 + 結束時使用記憶體
記憶體總量1 = 記憶體總量2
可以推出下面的式子:
開始時空閑記憶體 - 結束時空閑記憶體
= 記憶體總量1 - 開始時使用記憶體 - (記憶體總量2 - 結束時使用記憶體)
= 結束時使用記憶體 - 開始時使用記憶體
但是java api文檔中關於totalMemory方法的描述是:
返回 JAVA 虛擬機器中的記憶體總量。此方法返回的值可能隨時間的推移而變化,這取決於主機環境。
這句話是說記憶體總量是可能變化的, 也就是說,不能使用開始時空閑記憶體-結束時空閑記憶體得到結束時使用記憶體 - 開始時使用記憶體,也就是記憶體使用量量.
那麼,我們該如果擷取運行時記憶體使用量量呢?請繼續往下看.
在java程式中擷取記憶體使用量量
我們先說說在java程式中擷取運行時記憶體,上代碼:
Runtime run = Runtime.getRuntime();System.in.read(); // 暫停程式執行// System.out.println("memory> total:" + run.totalMemory() + " free:" + run.freeMemory() + " used:" + (run.totalMemory()-run.freeMemory()) );run.gc();System.out.println("time: " + (new Date()));// 擷取開始時記憶體使用量量long startMem = run.totalMemory()-run.freeMemory();System.out.println("memory> total:" + run.totalMemory() + " free:" + run.freeMemory() + " used:" + startMem );String str = "";for(int i=0; i<50000; ++i){ str += i;}System.out.println("time: " + (new Date()));long endMem = run.totalMemory()-run.freeMemory();System.out.println("memory> total:" + run.totalMemory() + " free:" + run.freeMemory() + " used:" + endMem );System.out.println("memory difference:" + (endMem-startMem));/*run.gc();System.out.println("memory> total:" + run.totalMemory() + " free:" + run.freeMemory() + " used:" + (run.totalMemory()-run.freeMemory()) );*/
代碼中沒有過於複雜的邏輯,關於調用System.in.read方法暫停程式以及列印目前時間的原因我們到後面再解釋.
這個程式在我的電腦上的一次輸出為:
由於記憶體總量增長了5倍(記憶體總量就是在 工作管理員 中看到的記憶體佔中量),導致空閑記憶體也增長了,那麼開始時空閑記憶體 - 結束時空閑記憶體就是負的…
這不排除是記憶體回收的結果,各位可以多執行幾次看看結果:空閑記憶體確實是增長了.當然,各位也可以把代碼的那幾行注釋取消了,看看執行過記憶體回收以後的記憶體變化.
在代碼中擷取程式記憶體佔用量的優點:
- 不需要其他任何工具,就可以大致測出我們程式消耗的記憶體.
缺點是
- 可能jvm剛回收過垃圾,我們的程式就擷取了記憶體的使用量,導致結果嚴重偏低.不過這種機率還是很低的.
- 需要改動我們的程式.測試完成以後還需要刪除這些代碼,不小心可能引入bug.這才是最大的問題!
使用工具
使用效能分析工具可以很方便的擷取程式記憶體,cpu佔用等資訊.同時也不需要修改程式.java的效能分析工具還是不少的,我們這裡使用的是jvisualvm.它最大的優點就是JDK內建,安裝過JDK以後系統裡就有jvisualvm了.我們可以在%JAVA_HOME%/bin目錄中找到jvisualvm.exe,這個程式使用相當簡單,我們就不說使用方法了,唯一需要注意的就是visualvm”開啟”一個程式需要一段時間,如果在”開啟”程式時程式結束了,那麼我們什麼資訊也得不到,所以我們需要在程式開始出暫停程式.這就是我們在最開始處調用System.in.read方法的原因.
開啟visualvm,然後運行我們的程式,在VisualVM中雙擊開啟我們的程式進行分析.在監視標籤中可以查看記憶體使用量量等資訊.下面這幅圖和上面的運行結果相對應:
我們可以拿著張圖和我們的程式輸出結果中對應的時間點的記憶體占資訊用進行比對.來擷取更真實的記憶體佔用量.
visualvm應該是1秒擷取一次記憶體使用量量,而不是取這1秒的平均值,作為這1秒的記憶體佔用量,不然的話,上面那幅圖以及下面這幾幅圖是沒法解釋的:
最後再說一句,如果你的visualvm安裝了Visual GC外掛程式,那麼還可以擷取到記憶體回收的一些資訊,比如記憶體回收次數及大致頻率:
從圖片可以看出, 共回收了149次垃圾,在後面一段時間,也就是我們的那個迴圈,記憶體回收頻率很高.
OK,關於記憶體分析就說到這吧.如果有什麼地方說錯了,還請指教.
寫於 2015/05/15
擷取java程式運行時記憶體資訊