淺談JVM之使用JFR解決內存泄露
簡介
雖然java有自動化的GC,但是還會有內存泄露的情況。當然java中的內存泄露跟C++中的泄露不同。
在C++中所有被分配的內存對象都需要要程序員手動釋放。但是在java中並不需要這個過程,一切都是由GC來自動完成的。那麼是不是java中就沒有內存泄露瞭呢?
要回答這個問題我們首先需要界定一下什麼是內存泄露。如果說有時候我們不再使用的對象卻不能被GC釋放的話,那麼就可以說發生瞭內存泄露。
一個內存泄露的例子
我們舉一個內存泄露的例子,先定義一個大對象:
public class KeyObject { List<String> list = new ArrayList<>(200); }
然後使用它:
public class TestMemoryLeak { public static HashSet<Object> hashSet= new HashSet(); public static void main(String[] args) throws InterruptedException { boolean flag= true; while(flag){ KeyObject keyObject= new KeyObject(); hashSet.add(keyObject); keyObject=null; Thread.sleep(1); } System.out.println(hashSet.remove(new KeyObject())); } }
在這個例子中,我們將new出來的KeyObject對象放進HashSet中。
然後將keyObject置為空。
但是因為類變量hashSet還保留著對keyObject的引用,所以keyObject對象並不會被回收。
註意,最後一行我們加瞭一個hashSet.remove的代碼,來使用類變量hashSet。
為什麼要這樣做呢?這樣做是為瞭防止JIT對代碼進行優化,從而影響我們對內存泄露的分析。
使用JFR和JMC來分析內存泄露
Flight Recorder(JFR)主要用來記錄JVM的事件,我們可以從這些事件中分析出內存泄露。
可以通過下面的指令來開啟JFR:
java -XX:StartFlightRecording
當然我們也可以使用java神器jcmd來開啟JFR:
jcmd pid JFR.dump filename=recording.jfr path-to-gc-roots=true
這裡我們使用JMC來圖形化分析一下上面的例子。
開啟JMC,找到我們的測試程序,打開飛行記錄器。
可以看到我們的對象在飛行記錄器期間分配瞭4MB的內存,然後看到整體的內存使用量是穩步上升的。
我們什麼時候知道會有內存泄露呢?最簡單的肯定就是OutOfMemoryErrors,但是有些很隱蔽的內存泄露會導致內存使用緩步上漲,這時候就需要我們進行細致的分析。
通過分析,我們看到內存使用在穩步上漲,這其實是很可疑的。
接下來我們通過JVM的OldObjectSample事件來分析一下。
OldObjectSample
OldObjectSample就是對生命周期比較長的對象進行取樣,我們可以通過研究這些對象,來檢查潛在的內存泄露。
這裡我們關註一下事件瀏覽器中的Old Object Sample事件,我們可以在左下方看到事件的詳情。
或者你可以使用jfr命令直接將感興趣的事件解析輸出:
jfr print –events OldObjectSample flight_recording_1401comflydeanTestMemoryLeak89268.jfr > /tmp/jfrevent.log
我們看一個具體的輸出Sample:
jdk.OldObjectSample {
startTime = 19:53:25.607
allocationTime = 19:50:51.924
objectAge = 2 m 34 s
lastKnownHeapUsage = 3.5 MB
object = [
java.lang.Object[200]
]
arrayElements = 200
root = N/A
eventThread = “main” (javaThreadId = 1)
stackTrace = [
java.util.ArrayList.<init>(int) line: 156
com.flydean.KeyObject.<init>() line: 11
com.flydean.TestMemoryLeak.main(String[]) line: 17
]
}
lastKnownHeapUsage是heap的使用大小,從日志中我們可以看到這個值是一直在增加的。
allocationTime表示的是這個對象分配的時間。
startTime表示的是這個對象被dump的時間。
object表示的是分配的對象。
stackTrace表示的是這個對象被分配的stack信息。
註意,如果需要展示stackTrace信息,需要開啟-XX:StartFlightRecording:settings=profile選項。
從上面的日志我們可以分析得出,main方法中的第17行,也就是 KeyObject keyObject= new KeyObject(); 在不斷的創建新的對象。
從而我們可以進行更深層次的分析,最終找到內存泄露的原因。
以上就是淺談JVM之使用JFR解決內存泄露的詳細內容,更多關於JVM之使用JFR解決內存泄露的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- 詳解JDK9特性之JPMS模塊化
- Java基礎之詳解HashSet的使用方法
- java貪心算法初學感悟圖解及示例分享
- Java 深入淺出掌握Collection單列集合Set
- Python入門基礎之數字字符串與列表