雖然java有自動化的GC,可是還會有內存泄露的狀況。固然java中的內存泄露跟C++中的泄露不一樣。java
在C++中全部被分配的內存對象都須要要程序員手動釋放。可是在java中並不須要這個過程,一切都是由GC來自動完成的。那麼是否是java中就沒有內存泄露了呢?程序員
要回答這個問題咱們首先須要界定一下什麼是內存泄露。若是說有時候咱們再也不使用的對象卻不能被GC釋放的話,那麼就能夠說發生了內存泄露。瀏覽器
內存泄露的主要緣由就是java中的對象生命週期有長有短。若是長生命週期的對象引用了短生命週期的對象,就有可能形成事實上的內存泄露。jvm
咱們舉一個內存泄露的例子,先定義一個大對象:測試
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置爲空。spa
可是由於類變量hashSet還保留着對keyObject的引用,因此keyObject對象並不會被回收。日誌
注意,最後一行咱們加了一個hashSet.remove的代碼,來使用類變量hashSet。
爲何要這樣作呢?這樣作是爲了防止JIT對代碼進行優化,從而影響咱們對內存泄露的分析。
Flight Recorder(JFR)主要用來記錄JVM的事件,咱們能夠從這些事件中分析出內存泄露。code
能夠經過下面的指令來開啓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就是對生命週期比較長的對象進行取樣,咱們能夠經過研究這些對象,來檢查潛在的內存泄露。
這裏咱們關注一下事件瀏覽器中的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(); 在不斷的建立新的對象。
從而咱們能夠進行更深層次的分析,最終找到內存泄露的緣由。
本文經過JFR和JMC的使用,介紹瞭如何分析內存泄露。但願你們可以喜歡。
本文做者:flydean程序那些事本文連接:http://www.flydean.com/jvm-diagnostic-memory-leak/
本文來源:flydean的博客
歡迎關注個人公衆號:程序那些事,更多精彩等着您!