1. 程式人生 > >troubleshoot之:使用JFR解決記憶體洩露

troubleshoot之:使用JFR解決記憶體洩露

[toc] # 簡介 雖然java有自動化的GC,但是還會有記憶體洩露的情況。當然java中的記憶體洩露跟C++中的洩露不同。 在C++中所有被分配的記憶體物件都需要要程式設計師手動釋放。但是在java中並不需要這個過程,一切都是由GC來自動完成的。那麼是不是java中就沒有記憶體洩露了呢? 要回答這個問題我們首先需要界定一下什麼是記憶體洩露。如果說有時候我們不再使用的物件卻不能被GC釋放的話,那麼就可以說發生了記憶體洩露。 記憶體洩露的主要原因就是java中的物件生命週期有長有短。如果長生命週期的物件引用了短生命週期的物件,就有可能造成事實上的記憶體洩露。 # 一個記憶體洩露的例子 我們舉一個記憶體洩露的例子,先定義一個大物件: ~~~java public class KeyObject { List list = new ArrayList<>(200); } ~~~ 然後使用它: ~~~java public class TestMemoryLeak { public static HashSet 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 java -XX:StartFlightRecording ~~~ 當然我們也可以使用java神器jcmd來開啟JFR: ~~~java jcmd pid JFR.dump filename=recording.jfr path-to-gc-roots=true ~~~ 這裡我們使用JMC來圖形化分析一下上面的例子。 ![](https://img-blog.csdnimg.cn/20200704192328179.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_35,color_8F8F8F,t_70) 開啟JMC,找到我們的測試程式,開啟飛行記錄器。 ![](https://img-blog.csdnimg.cn/20200704192538997.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_35,color_8F8F8F,t_70) 可以看到我們的物件在飛行記錄器期間分配了4MB的記憶體,然後看到整體的記憶體使用量是穩步上升的。 > 我們什麼時候知道會有記憶體洩露呢?最簡單的肯定就是OutOfMemoryErrors,但是有些很隱蔽的記憶體洩露會導致記憶體使用緩步上漲,這時候就需要我們進行細緻的分析。 通過分析,我們看到記憶體使用在穩步上漲,這其實是很可疑的。 接下來我們通過JVM的OldObjectSample事件來分析一下。 # OldObjectSample OldObjectSample就是對生命週期比較長的物件進行取樣,我們可以通過研究這些物件,來檢查潛在的記憶體洩露。 ![](https://img-blog.csdnimg.cn/20200704203127379.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_35,color_8F8F8F,t_70) 這裡我們關注一下事件瀏覽器中的Old Object Sample事件,我們可以在左下方看到事件的詳情。 或者你可以使用jfr命令直接將感興趣的事件解析輸出: ~~~java jfr print --events OldObjectSample flight_recording_1401comflydeanTestMemoryLeak89268.jfr > /tmp/jfrevent.log ~~~ 我們看一個具體的輸出Sample: ~~~java 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.(int) line: 156 com.flydean.KeyObject.() 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/](http://www.flydean.com/jvm-diagnostic-memory-leak/) > > 本文來源:flydean的部落格 > > 歡迎關注我的公眾號:程式那些事,更多精彩等著您!