java 堆内存泄露排查(例子)

文章目录

  • 配置说明
  • 排查之路
    • 视频教程
    • 图文教程
  • 数据表格
  • 相关链接
  • 后记


配置说明

  • 系统: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 任务管理器内存情况
    java 堆内存泄露排查(例子)_第1张图片

由于资源管理器并不能显示更多的内存消息,所以借助 JConsole 查看内存的泄露类型(堆内img2/堆外img3

  • img2 堆内存
    java 堆内存泄露排查(例子)_第2张图片

  • img3 非堆内存
    java 堆内存泄露排查(例子)_第3张图片

有上图可知,此处发生的是堆内存泄露,非堆内存处于可接受范围内,为了查看堆内存细节,这里使用 Memory Analyzer 做内存分析

  • Memory Analyzer 安装使用请参考这两篇教程
    1. 内存管理工具Memory Analyzer的使用
    2. Memory Analyzer Tool的使用

参照以上教程,排查内存泄露触发点

整体流程:限制内存大小,制造溢出,查找溢出

  • 鼠标右键点击主类 KeyboardPiano => Run As => Run Configurations

  • 设置以下参数

    1. -Xms50m JVM初始分配的堆内存,设置最小堆内存为 50 M
    2. -Xmx50m JVM最大允许分配的堆内存,设置最大堆内存为 50 M
    3. -XX:+HeapDumpOnOutOfMemoryError 当出现 OOM 时进行 HeapDump
    4. -XX:HeapDumpPath 设置 dump 文件输出路径 (请读者务必修改成自己的路径)
-Xms50m -Xmx50m
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=E:\Java_code\piano_tmp\memory_test
  • img4 图里面参数仅作参考,以上面代码为准
    java 堆内存泄露排查(例子)_第4张图片

  • 在一系列猛如虎的按键操作后,JVM 承受不住外界的压力,果断溢出报错

  • img5 溢出报错
    java 堆内存泄露排查(例子)_第5张图片

  • 接着在 eclipse 打开 *.hprof 文件,选择 Leak Suspects Report, 点击 Finish

  • 分析查看可疑点,博主在 Problem Suspect 2 发现异常,如下图

  • img6 Problem Suspect 2
    java 堆内存泄露排查(例子)_第6张图片

  • 说明 DirectClip 有问题,切换到 Overview 窗口,点击查看 Dominator Tree

  • 按照 Retained Heap 排序,可以看到一堆的 DirectClip

  • 随便找一个展开,发现 byte[] 占据了绝大多数的内存,列为重点怀疑对象

  • img7 Dominator Tree
    java 堆内存泄露排查(例子)_第7张图片

  • 接着鼠标右键点开其中一个 DirectClip 对象,选择 List objects => with outgoing references 查看外部引用

  • 展开后发现,byte[] audioData 就是罪魁祸首,从名字可以看出,音频的数据就保存在该 byte 数组

  • img8 DirectClip 内存分配情况
    java 堆内存泄露排查(例子)_第8张图片

  • 最后,根据博主测试得出,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就介入了进来,它会回收空间,并使堆中的对象排列更紧凑。这样,就始终会有足够大的内存空间可以分配。

  • 内存分类 深入浅出的典例

    1. 伊甸园空间(堆):大多数对象最初分配内存的池
    2. 生存空间(堆):包含伊甸园空间垃圾收集后生存的对象。
    3. 年老代(堆):池包含已经存在一段时间的对象
    4. 永久代(非堆):池包含的所有虚拟机本身的反射的数据,如类和方法的对象。 Java虚拟机,使用类数据共享,这一代分为只读和读写区域。
    5. 代码缓存(非堆):HotSpot Java虚拟机的还包括一个代码缓存,包含内存,使用本机代码的编译和存储。
  • 缓存机制详解

    1. JAVA面试——缓存
    2. 用Java实现多种缓存机制
  • Linux 堆外内存的排查参考

    1. DirectByteBuffer堆外内存溢出问题排查
    2. 记一次JVM堆外内存泄露Bug的查找
    3. 使用google perf工具来排查堆外内存占用

后记

内存泄露是纯代码层面的问题, 而内存泄露处理则是为了提高程序的健壮性

你可能感兴趣的:(Debug,java,内存泄露排查,Debug)