记录一次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的数据,只能等下一次发生,但有些情况几个月才发生一次,建议预先配置。

 

posted @ 2020-06-08 11:33  mikevictor  阅读(421)  评论(0编辑  收藏  举报