Java 使用 VisualVM 排查内存泄露

这篇具有很好参考价值的文章主要介绍了Java 使用 VisualVM 排查内存泄露。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 问题发生

线上突发告警,笔者负责的一个服务老年代内存使用率到达 75% 阈值,于是立即登录监控系统查看数据。拉长时间周期,查看最近 7 天的 GC 和老年代内存占用,监控截图如下。可以看到老年代占用内存的最低点在逐步抬升,初步判断是发生了内存泄露

java visualvm,JVM,随笔,java,jvm,linux

2. 排查过程

2.1 初步排查

从监控上看,这个服务的两个实例老年代内存占用情况并不一致,其中疑似发生内存泄露的是跑脚本的机器。于是登录到目标机器,首先执行 jmap -histo 1 | head -n 100 命令查看目标进程的堆内存占用前 100 的对象,发现其中 SkyWalking 的一个 trace 追踪对象 NoopSpan 实例总数达到了 2600 万之巨,内存占用也达到 600M,明显不正常
java visualvm,JVM,随笔,java,jvm,linux

2.2 Visual VM 内存分析

由于生产环境控制严格,不允许在线 dump 堆内存数据,于是在预发环境执行 jmap -dump:format=b,file=/tmp/dump 1 命令,将有相同问题的 java 进程的堆内存 dump 下来。下载拿到 dump 文件后,需要打开 VisualVM 加载该文件,以下为操作步骤

  1. 首先打开 VisualVM,点击截图中的按钮加载 dump 文件
    java visualvm,JVM,随笔,java,jvm,linux

  2. dump 文件加载后,点击截图中框出来的按钮,切换选项卡为查看对象
    java visualvm,JVM,随笔,java,jvm,linux

  3. 由于笔者初步排查已经确定了可疑的实例为 NoopSpan,故在对象选项卡界面直接过滤该对象,并展示其 相关引用、GC root需注意 GC root 是引用链的起点,从 VisualVM 的分析可以看到 NoopSpan 的实例都是以 LinkedList 节点的形式存在,引用链条为 FastThreadLocalThread -> threadLocals(ThreadLocalMap) -> table(ThreadLocalMap$Entry[] 数组) -> [1](ThreadLocalMap$Entry 数组第一个元素) -> value(键值对 ThreadLocalMap$Entry 的值) -> activeSpanStack (SkyWalking 的 TracingContext 内部暂存 span 的 LinkedList) -> 链表的一级级前后指针,至此可以猜测是 ThradLocal 使用不当(例如 ThreadLocal 使用后没有remove)导致内存泄露
    java visualvm,JVM,随笔,java,jvm,linux

  4. 确定了引用链,则可以看到 NoopSpan 应该是被封装为 LinkedList 的节点被保存在对象TracingContext#1 的内部链表 activeSpanStack 中。此时查看该对象的链表的具体元素数据,可以看到总共有1万多个元素,点开第一个节点,查看该 LocalSpan 的名称,确定当前 SkyWalking 的 trace 记录的起点为这个 LocalSpan 的创建
    java visualvm,JVM,随笔,java,jvm,linux

2.3 代码分析

  1. 在项目中搜索上一节分析出的 LocalSpan 名称,发现创建该 Span 主要是为了在多线程环境下跨线程传递 trace,创建入口为 ContextManager#createLocalSpan() 方法。这个方法会创建 Trace 上下文对象 TracingContext 并将其设置到 ThreadLocal 中,创建出 TracingContext 对象后还会调用其相关方法创建 LocalSpan 对象,并将创建的 LocalSpan 对象存入 TracingContext 内部的 activeSpanStack 链表。至此基本印证了 VisualVM 的引用分析,大致确定是 ThreadLocal 的使用导致了内存泄露

     public static AbstractSpan createLocalSpan(String operationName) {
         operationName = StringUtil.cut(operationName, OPERATION_NAME_THRESHOLD);
         AbstractTracerContext context = getOrCreate(operationName, false);
         return context.createLocalSpan(operationName);
     }
    
     private static AbstractTracerContext getOrCreate(String operationName, boolean forceSampling) {
         AbstractTracerContext context = CONTEXT.get();
         if (context == null) {
             if (StringUtil.isEmpty(operationName)) {
                 if (logger.isDebugEnable()) {
                     logger.debug("No operation name, ignore this trace.");
                 }
                 context = new IgnoredTracerContext();
             } else {
                 if (EXTEND_SERVICE == null) {
                     EXTEND_SERVICE = ServiceManager.INSTANCE.findService(ContextManagerExtendService.class);
                 }
                 context = EXTEND_SERVICE.createTraceContext(operationName, forceSampling);
             }
             CONTEXT.set(context);
         }
         return context;
     }
    
    
  2. 我们知道,在线程池环境下使用 ThreadLocal 如果忘记 remove 很容易发生内存泄漏。继续阅读源码,发现 ThreadLocal 被移除的触发点在 ContextManager#stopSpan() 方法,该方法每调用一次就会将之前添加到 TracingContext 内部的 activeSpanStack 链表中的 Span 移除,直到链表元素数量为 0 才会去 remove 掉 ThreadLocal

     public static void stopSpan() {
         final AbstractTracerContext context = get();
         if (Objects.isNull(context)) {
             return;
         }
         stopSpan(context.activeSpan(), context);
    
     }
     private static void stopSpan(AbstractSpan span, final AbstractTracerContext context) {
         try {
             if (context.stopSpan(span)) {
                 CONTEXT.remove();
                 RUNTIME_CONTEXT.remove();
             }
         } catch (Throwable t) {
             //
         }
     }
    
  3. 此时回到项目代码一看,问题一目了然,代码中创建了 LocalSpan 但是没有调用相关方法把它 stop 掉,导致 LocalSpan 一直在 TracingContext 内部的 activeSpanStack 链表中堆积,并且由于链表前后指针的存在无法回收,最终导致了内存泄漏文章来源地址https://www.toymoban.com/news/detail-617027.html

到了这里,关于Java 使用 VisualVM 排查内存泄露的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • VisualVM工具的下载及插件安装

    VisualVM作为GitHub的独立工具分发,并作为GraalVM的可选组件分发。两者都是具有相同功能的相同位。独立工具在任何兼容的JDK上运行,捆绑工具配置为使用主机GraalVM运行。 VisualVM也作为Java VisualVM在Oracle JDK 6~8中分发。它已在Oracle JDK 9中停产。请参阅升级 Java VisualVM 页面,了解如

    2024年02月12日
    浏览(46)
  • 【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍

    本文主要针对于综合层面上进行分析JVM优化方案总结和列举调优参数计划。主要包含: 调优之逃逸分析(栈上分配) 调优之线程局部缓存(TLAB) 调优之G1回收器 -XX:+DoEscapeAnalysis 逃逸分析(Escape Analysis) 逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定

    2024年01月25日
    浏览(65)
  • Java线上故障排查(CPU、磁盘、内存、网络、GC)+JVM性能调优监控工具+JVM常用参数和命令

    根据服务部署和项目架构,从如下几个方面排查: (1)运用服务器:排查内存,cpu,请求数等; (2)文件图片服务器:排查内存,cpu,请求数等; (3)计时器服务器:排查内存,cpu,请求数等; (4)redis服务器:排查内存,cpu,连接数等; (5)db服务器:排查内存,cpu,连接数

    2024年02月07日
    浏览(67)
  • jvm内存溢出排查(使用idea自带的内存泄漏分析工具)

    想分析堆内存溢出,一定在运行jar包时就写上参数 -XX:+HeapDumpOnOutOfMemoryError ,可以看我之前关于如何运行jar包的文章。若你没有写。可以写上参数,重启你的项目,等你的项目发生下一次堆内存溢出异常,在运行的同级文件夹,将产生类似这样一个文件 java_pid74935.hprof ,若你

    2024年02月09日
    浏览(58)
  • C++内存泄露排查的一个案例

    背景: 这熟悉的线条. 请求量没啥波动, 不用怀疑, 就是内存泄露了. 方案一 Valgrind Valgrind可以用来检测是否有非法使用内存的问题, 如: 访问未初始化的内存,访问数组越界, 忘记释放动态内存的问题; 首先需要定位是哪个进程的内存泄露. 使用top命令, 然后shift+m按照内存排序, 找

    2024年02月13日
    浏览(37)
  • Java 内存泄露问题详解

    目录 1、什么是内存泄露? 2、Java 中可能导致内存泄露的场景 3、长生命周期对象持有短生命周期对象引用造成的内存泄露问题示例 4、静态集合类持有对象引用造成内存泄露问题的示例 1、什么是内存泄露?         内存泄露指的是程序运行时未能正确释放不再使用的内

    2024年02月09日
    浏览(42)
  • Linux C/C++ 程序内存泄露排查

    由于C/C++程序可以动态申请内存,动态申请的内存位于程序的队区,如果程序比较复杂,程序员在编写代码的时候不小心,可能会存在申请了内存没有释放的情况,程序长期运行,会导致系统中用户程序可分配堆内存越来越少的,最终程序OOM崩溃。 /proc/meminfo 文件保存了系统

    2024年02月03日
    浏览(37)
  • C++经典面试题:内存泄露是什么?如何排查?

    1.内存泄露的定义:内存泄漏简单的说就是申请了⼀块内存空间,使⽤完毕后没有释放掉。 它的⼀般表现⽅式是程序运⾏时间越⻓,占⽤内存越多,最终⽤尽全部内存,整个系统崩溃。由程序申请的⼀块内存,且没有任何⼀个指针指向它,那么这块内存就泄漏了。 2.如何检测

    2024年02月07日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包