记录一次Java内存泄露分析过程
版权说明: 本文章版权归本人及博客园共同所有,转载请在文章前标明原文出处( https://www.cnblogs.com/mikevictor07/p/13032635.html ),以下内容为个人理解,仅供参考。
一、背景
最近在集成一个调用链客户端jaeger client(一种opentracing的实现)集成到自定义的SDK中,我们需要增加一种特性:能通过远端服务动态更新采样的参数。
按照jaeger的实现,如果关键参数(比如队列大小、buffer冲刷间隔等)的变动【必须重新建立一个比较重的Tracer】(可以理解为一个给上层调用的对象,无需了解细节),
按照多年的直觉(老油条),这个Tracer大对象的创建成本高,有泄露的可能。
二、使用方法
按照调用规范,我们的重建tracer这个对象后对旧的tracer进行close,源码如下:
/** * Shuts down the {@link Reporter} and {@link Sampler} */ @Override public void close() { reporter.close(); sampler.close(); }
按理说调用close即可。
三、验证步骤
在这里使用了JProfiler来调试,启用应用后模拟每秒钟创建一次tracer,并且创建后关闭原先的tracer,然后通过JProfiler的Live Memory观察如下:
这里使用tracer这个关键词过滤信息快速找到相关对象,在点击上图的【Run GC】按钮后其他对象能回收,只有排名前两个对象无法进行回收,
说明它们存在强引用无法GC,而且数量一直在增长,右键第一行,选择【Show Select In Heap Walker】,如下:
选择进去后提示是否dump内存分析(若应用占用内存较大无法本地dump,可用memory analyzer tool继续dump hprof文件分析):
从references分析有哪些引用,结果如下:
从这里基本可以看出是JVM的shutdown hook强引用,我们再源码查找showdown hook的调用,结果如下:
if (builder.manualShutdown || runsInGlassFish()) { log.info("No shutdown hook registered: Please call close() manually on application shutdown."); } else { // register this tracer with a shutdown hook, to flush the spans before the VM shuts down Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { JaegerTracer.this.close(); } }); }
由于默认没有开启manual shutdown,jaeger为了应用停止时能close(内部主要是冲刷数据到agent)加了此特性。
四、修复与总结
修复方法很简单,将manual shutdown配置为true,并且自行实现关闭钩子即可(在我们的应用中是将tracer根据服务名put到map中,后一次对象将覆盖前一个,最后循环map关闭)。
本文章展示了使用jprofile来分析一次内存泄露的过程,如果对生产的memory分析,建议dump hprof文件到本地。
启动参数加上-XX:+HeapDumpOnOutOfMemoryError 以便保留当时内存快照,如果发生OOM后JVM杀掉触发OOM的线程,部分内存也被回收,事后分析的dump文件可能已经没有了原先到OOM的数据,只能等下一次发生,但有些情况几个月才发生一次,建议预先配置。