前言
内存泄露和内存溢出的区别:内存泄露从老年代的增长情况看是缓慢上升的, 最终达到老年代上限才会导致溢出,有些内存泄露可能需要很长的时间发生, 所以说内存泄露更隐蔽, 不像内存溢出那样容易暴露(内存溢出直接抛出OOM), 而且内存长时间得不到释放会导致服务性能越来越差、gc时间变长、响应变慢:
1. 安装
在Eclipse help -> Eclipse Marketplace下搜索Memory:
按照操作流程安装完成,重启Eclipse.
2.获取Heapdump 文件
方法1
**/jdk/1.7.0_95l64/bin/jmap -dump:format=b,file=heapdump_name.hprof <pid>
方法2.
如果你的是spring boot项目且暴露了actuator里面的heapdump的api. 直接用下面的命令。
*/actuator/heapdump
可以每隔一段时间获取一个heapdump文件
3.Histogram查询
利用Elipse 里Memory Analysis view打开你想分析的heapdump文件
打开Histogram
它按类名将所有的实例对象列出来,可以点击表头进行排序,在表的第一行可以输入正则表达式来匹配结果:
4. 使用 Compare Basket 功能分析
利用Elipse 里Memory Analysis view依次打开多个heapdump文件
1.菜单栏 window → compare basket ,打开比较窗口(如果最下面一栏已经有compare basket则这步不需要),如下图:
2. 依次打开3个dump的dashboard面板, 在下方的 Actions一栏点击"histogram"或"dominator tree"生成对应的直方图或支配树列表,如下图:
直方图或支配树都可以列出堆中存活的所有对象,但二者的维度不同, 直方图按照类型统计, 支配树是以对象维度统计。
如果你对项目代码比较熟悉, 通过直方图定位内存泄露会更快,因为它是按照类型全部平铺开的,如果这个项目不是你负责的, 建议使用支配树的方式, 因为支配树包含了对象之间的引用关系(支配树视图可以展开查看内部引用层级)
3. 我们以支配树做比对, 在最下面一栏的"Navigation History (window → navigation history)"里(直方图类似)找到在第2步打开的支配树dominator tree图标, 右键添加到compare basket, 如下图:
添加好后,打开Compare Basket面板,得到结果:
4.重复上面的2, 3步骤依次把其他的dump文件添加到"compare basket"栏, 然后点击右上角的红色感叹号, 生成比较结果,如下图:
(注意比较的dump文件的顺序,时间最早的在上面,可以通过右上角的上箭头↑和下箭头↓调整顺序)
生成的比对结果如下:
Shallow Heap一列后面的序号 #0, #1, #2 分别对应:
第一个dump文件占用的shallow size, 第二个dump文件占用的shallow size , 第三个dump文件占用的shallow size
Retained Heap #0, Retained Heap #1, Retained Heap #2 这3列分别对应:
第一个dump文件占用的retained size, 第二个dump文件占用的retained size , 第三个dump文件占用的retained size
红框圈出的是内存连续增长的对象, 可以通过右边红框的retained heap看出内存变大的趋势
绿框圈出的是没有变化的对象(至少在这3次比较中没有变化),
蓝框圈出的是内存占用下降的对象
一般我们主要关注红框标出的对象, 因为这部分发生内存泄露的嫌疑最大
这里先区分两个概念:
Shallow Size
对象自身占用的内存大小,不包括它引用的对象。
针对非数组类型的对象,它的大小就是对象与它所有的成员变量大小的总和。
针对数组类型的对象,它的大小是数组元素对象的大小总和。
Retained Size
Retained Size=当前对象大小+当前对象可直接或间接引用到的对象的大小总和。(间接引用的含义:A->B->C, C就是间接引用)
Retained Size就是当前对象被GC后,从Heap上总共能释放掉的内存。
因为这里我们比较的是支配树, 所以按照retained heap倒序排列, 从左到右依次为: retained heap #0 → retained heap #1 → retained heap #2(以最后一个retained heap #2 倒序, 因为这个是最后一次dump的内存快照, 这样可以看出内存泄露的增长趋势)
5. 使用 Path To Gc Root 定位业务代码
还有另外一个内存泄露的嫌疑是"com.*.common.utils.ITextRendererPoolManager
", 如上面比对结果的图:
单独在dominator tree支配树视图展开如图所示:
ITextRendererPoolManager
内部使用了apache的一个对象缓冲池, 目的可能是为了对象复用, 继续展开,如下图:
发现是pdf的一个工具类:org.xhtmlrenderer.pdf.ITextRenderer, 这个开源的pdf工具是我们项目的邮件功能在发送附件的时候生成pdf文档时引入的一个第三方jar包,开始怀疑是否是这个开源的pdf工具导致的内存泄露, 但是不清楚这个jar包是在哪里调用的?
这里可以通过"path to gc root"查看是谁在引用他, 即我们业务代码调用的地方,如下图:
这里先说下"path to gc root"选项的含义:
with all references : 所有引用, 包括强引用, 弱引用, 软引用, 虚引用
exclude weak reference : 排除弱引用
exclude soft reference : 排除软引用
。。。。
我们知道软引用, 弱引用这些在发生full gc时可能会被回收掉(回收时机不同, 具体可自行百度), 目的是不造成内存溢出。 一般引起内存溢出的都是强引用,所以你可以选择"exclude all ptantom/weak/soft reference"只查看强引用。