配置說明
- 系統:Windows10
- 項目:KeyboardPiano V1.7
- 對象:音頻播放類
com.sun.media.sound.DirectAudioDevice$DirectClip
- 原因:sun 的老舊框架,
Clip.close()
,音頻數據audioData[]
無法釋放,從而導致堆內存泄露 - 工具:JConsole、Memory Analyzer、Eclipse
項目簡介,KeyboardPiano 是基於
java
實現的鍵盤鋼琴,其原理是按一個鍵播放一段音頻
排查之路
視頻教程
- Memory Analyzer 內存泄露排查過程 其他細節請見 圖文教程
注意:請讀者務必安裝 Memory Analyzer,才能進行相應操作
圖文教程
-
運行項目,經過大量按鍵後,查看任務管理器,出現內存猛增的情況,且有增無減(img1)
-
img1 任務管理器內存情況
由於資源管理器並不能顯示更多的內存消息,所以藉助 JConsole 查看內存的泄露類型(堆內img2/堆外img3)
-
img2 堆內存
-
img3 非堆內存
有上圖可知,此處發生的是堆內存泄露,非堆內存處於可接受範圍內,爲了查看堆內存細節,這裏使用 Memory Analyzer 做內存分析
- Memory Analyzer 安裝使用請參考這兩篇教程
參照以上教程,排查內存泄露觸發點
整體流程:限制內存大小,製造溢出,查找溢出
-
鼠標右鍵點擊主類
KeyboardPiano
=> Run As => Run Configurations -
設置以下參數
-Xms50m
JVM初始分配的堆內存,設置最小堆內存爲 50 M-Xmx50m
JVM最大允許分配的堆內存,設置最大堆內存爲 50 M-XX:+HeapDumpOnOutOfMemoryError
當出現 OOM 時進行 HeapDump-XX:HeapDumpPath
設置 dump 文件輸出路徑 (請讀者務必修改成自己的路徑)
-Xms50m -Xmx50m
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=E:\Java_code\piano_tmp\memory_test
-
img4 圖裏面參數僅作參考,以上面代碼爲準
-
在一系列猛如虎的按鍵操作後,JVM 承受不住外界的壓力,果斷溢出報錯
-
img5 溢出報錯
-
接着在 eclipse 打開 *.hprof 文件,選擇 Leak Suspects Report, 點擊 Finish
-
分析查看可疑點,博主在 Problem Suspect 2 發現異常,如下圖
-
img6 Problem Suspect 2
-
說明
DirectClip
有問題,切換到 Overview 窗口,點擊查看 Dominator Tree -
按照 Retained Heap 排序,可以看到一堆的
DirectClip
-
隨便找一個展開,發現
byte[]
佔據了絕大多數的內存,列爲重點懷疑對象 -
img7 Dominator Tree
-
接着鼠標右鍵點開其中一個
DirectClip
對象,選擇 List objects => with outgoing references 查看外部引用 -
展開後發現,
byte[] audioData
就是罪魁禍首,從名字可以看出,音頻的數據就保存在該byte
數組 -
img8
DirectClip
內存分配情況
-
最後,根據博主測試得出,
DirectClip
即使關閉了,audioData
也得不到釋放,才導致了堆內存泄露。所以可以說,這是 sun 老舊框架的 BUG,並非自身程序的問題 -
那泄露的關鍵點已經找到了,如何修正 BUG 呢?詳細見 KeyboardPianoV1.7.2 Debug(音頻優化) 關鍵在於更換播放方式
數據表格
- JConsole 內存分析數據表(主要參考數據爲高亮部分),內存分類以及相關拓展資料請見 相關鏈接
Memories | initialized | increasing | total |
---|---|---|---|
1. Heap Memory Usage | 100 | 250 | 350 |
2. Non-Heap Memory Usage | 22 | 10 | 32 |
3. Memory Pool "PS Old Gen" | 0 | 220 | 220 |
4. Memory Pool "PS Eden Space" | 100 | 0 | 100 |
5. Memory Pool "PS Survivor Space" | 0 | 22 | 22 |
6. Memory Pool “Metaspace” | 16 | 1 | 17 |
7. Memory Pool “Code Cache” | 5 | 7 | 12 |
8. Memory Pool “Compressed Class Space” | 2 | 0 | 2 |
相關鏈接
-
內存管理機制 ← 以下說明摘抄於該教程
通常,會認爲在堆上分配對象的代價比較大,但是GC卻優化了這一操作:
C++中,在堆上分配一塊內存,會查找一塊適用的內存加以分配,如果對象銷燬,這塊內存就可以重用;
而Java中,就想一條長的帶子,每分配一個新的對象,Java的“堆指針”就向後移動到尚未分配的區域
但是這種工作方式有一個問題:如果頻繁的申請內存,資源將會耗盡。這時GC就介入了進來,它會回收空間,並使堆中的對象排列更緊湊。這樣,就始終會有足夠大的內存空間可以分配。 -
內存分類 深入淺出的典例
- 伊甸園空間(堆):大多數對象最初分配內存的池。
- 生存空間(堆):包含伊甸園空間垃圾收集後生存的對象。
- 年老代(堆):池包含已經存在一段時間的對象。
- 永久代(非堆):池包含的所有虛擬機本身的反射的數據,如類和方法的對象。 Java虛擬機,使用類數據共享,這一代分爲只讀和讀寫區域。
- 代碼緩存(非堆):HotSpot Java虛擬機的還包括一個代碼緩存,包含內存,使用本機代碼的編譯和存儲。
-
緩存機制詳解
-
Linux 堆外內存的排查參考
後記
內存泄露是純代碼層面的問題, 而內存泄露處理則是爲了提高程序的健壯性