Java 使用 VisualVM 排查内存泄露

news2024/12/22 18:08:44

文章目录

  • 1. 问题发生
  • 2. 排查过程
      • 2.1 初步排查
      • 2.2 Visual VM 内存分析
      • 2.3 代码分析

1. 问题发生

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

在这里插入图片描述

2. 排查过程

2.1 初步排查

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

2.2 Visual VM 内存分析

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

  1. 首先打开 VisualVM,点击截图中的按钮加载 dump 文件
    在这里插入图片描述

  2. dump 文件加载后,点击截图中框出来的按钮,切换选项卡为查看对象
    在这里插入图片描述

  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)导致内存泄露
    在这里插入图片描述

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

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 链表中堆积,并且由于链表前后指针的存在无法回收,最终导致了内存泄漏

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/624116.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Transformer【ViT】

参考 导师!博主的复现太细了。做个记录。 层神经网络学习小记录67——Pytorch版 Vision Transformer(VIT)模型的复现详解 计算机视觉中的transformer模型创新思路总结_Tom Hardy的博客-CSDN博 Vision Transformer详解 ViT 前处理 网络结…

2核4G轻量应用服务器性能测评(腾讯云PK阿里云)

阿里云轻量应用服务器2核4G4M带宽297.98元12个月,腾讯云轻量2核4G5M服务器168元一年,628元3年,2核4G轻量应用服务器阿里云和腾讯云怎么选择?哪个性能比较好?阿腾云分享轻量应用服务器2核4G配置阿里云和腾讯云CPU、带宽…

三种编码方式(费诺曼编码,霍夫曼编码,哈夫曼树编码)的简单解释和介绍

一. 费诺曼(Fano)编码是一种前缀编码,其基本原理是将出现频率较高的符号用短的编码表示,而出现频率较低的符号则用长的编码表示。通过这种方式进行编码,可以达到更好的压缩效果。 费诺曼编码的具体过程如下: 将要编码的符号按照…

书籍《银河帝国11:曙光中的机器人》观后感

这本书其实看完有段时间了,《银河帝国11:曙光中的机器人》是阿西莫夫写的《基地》系列第11本书,整个系列不是完全连贯的,本书是《银河帝国10》的后续。 先让我们来回忆一下前奏和背景吧,未来随着人类科技发展,遨游太空…

d2l学习——第一章Introduction

x.1 key components in ML 就和统计学习方法书中说的一样,机器学习也可以分为几个核心要义,Data, Models, Objective Functions, Optimization Algorithms, 其中: Data: 用来学习的数据Model: 如何转换/translate数据的模型Obje…

Vivado_除法器 IP核 使用详解

本文介绍使用Vivado中除法器Divider Generator(5.1)的使用方法。 参考资料:pg151 文章目录 Divider Generator仿真测试 Divider Generator Channel Settings选项卡 #Common Options: Algorithm Type: 一共有三种类型,…

操作系统 第三章 3.1 错题整理

装入时,相应的地址要修改 若存放的位置从150开始 则所有的地址都加150 加完后的地址是物理地址 不能改变 可变分区:产生的外部碎片需要紧凑 C D 都是执行时才进行地址的变换的 该共享段直接在内存中 不用调入调出 供多个进程共享 II 假设该进程很小 页面…

智能指针(1)

智能指针(1) 概念内存泄漏指针指针概念RAII使用裸指针存在的问题 智能指针使用分类unique(唯一性智能指针)介绍智能指针的仿写代码理解删除器 概念 内存泄漏 内存泄漏:程序中已动态分配的堆内存由于某些原因而未释放…

Vue.js 中的插槽是什么?如何使用插槽?

Vue.js 中的插槽是什么?如何使用插槽? 在 Vue.js 中,插槽是一种组件之间通信的机制,允许父组件向子组件传递内容,并在子组件中进行渲染。本文将介绍 Vue.js 中插槽的概念、优势以及如何使用插槽。 什么是插槽&#xf…

cas 6 单点登录登出管理

cas自带的登出是通过登出地址后面接的service地址进行跳转,但是对于service没有进行验证,这边我们网络渗透测试后说可能被钓鱼需要进行验证所以开始了以下操作。 1找资料 首先到cas官网找,发现项目有自带的是否跳转,跳转地址参数…

辞了外包,上岸字节测试岗我落泪了,400多个日夜没人知道我付出了多少....

前言: 没有绝对的天才,只有持续不断的付出。对于我们每一个平凡人来说,改变命运只能依靠努力幸运,但如果你不够幸运,那就只能拉高努力的占比。 2023年3月,我有幸成为了字节跳动的一名自动化测试工程师&am…

Qt导出pdf格式表格

预期目标如下: 头文件: #include #include #include #include #include #include #include private: QString m_html; 调用: QDateTime dateTime QDateTime::currentDateTime(); //当前日期和时间 QString ExportTime dateTime.t…

python带你获取小破站喜爱UP得所用内容

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 环境使用: Python 3.8 Pycharm 2022.3版本 ffmpeg <需要设置环境变量> 软件的使用 合成视频和音频 模块使用: 第三方模块&#xff0c;需要安装 import requests >>> pip install requests 内置模…

认识数据湖加速器(Data Lake Accelerator Goose FileSystem,GooseFS)

认识数据湖加速器Data Lake Accelerator Goose FileSystem&#xff0c;GooseFS 一、产品概述二、产品功能三、产品优势四、快速入门五、使用 GooseFS 预热 Table 中的数据六、使用 GooseFS 进行文件上传和下载操作七、使用 GooseFS 加速文件上传和下载操作八、关闭 GooseFS九、…

UE5 Chaos破碎系统学习2

本文继续从实用性的角度学习Chaos破碎系统&#xff0c;因为破碎的许多操作需要力场&#xff0c;比较麻烦&#xff0c;因此本文打算绕过力场实现一些效果&#xff1a; 1.显示材质效果 制作Chaos破碎效果时&#xff0c;会在编辑器下看不见材质&#xff0c;我们可以选择Geometr…

美创数据安全服务能力再获认可!

美创数据安全服务能力又一次获认可&#xff01; 近日&#xff0c;经全方位能力评估和专家评审&#xff0c;美创获得中国软件评测中心和中国计算机行业协会数据安全专业委员会联合颁发的《数据安全服务能力评定资格证书》&#xff0c;数据安全评估能力符合二级评定资格要求。 为…

生存压力下,Smartbi这套方案为企业降本增效带来新的希望

在如今的经济环境下&#xff0c;许多IT企业都面临着困境。经济的不景气导致市场竞争更加激烈&#xff0c;企业的盈利能力受到了严重的冲击&#xff1b;高昂的成本让企业喘不上气来。为了在这个艰难的时期生存下来&#xff0c;降本增效成为了企业的当务之急。 l实施项目利润低&a…

【C++】是内存管理,但C++ !! 模板初阶

目录 一&#xff0c;回望C语言内存 二&#xff0c; C 内存管理方式 1. 内置类型 2. 自定义类型 3. new & malloc 返回内容区别 4. operator new & operator delete 5. malloc/free和new/delete的区别总结 6. 定位new表达式(placement-new) &#xff08;了…

018+limou+C语言预处理

0.前言 您好&#xff0c;这里是limou3434的一篇博客&#xff0c;感兴趣您可以看看我的其他博文系列。本次我主要给您带来了C语言有关预处理的知识。 1.宏的深度理解与使用 1.1.数值宏常量 #define PI 3.1415926注意define和#之间是可以留有空格的 1.2.字符宏常量 #includ…

是德DSO9254A示波器/KEYSIGHT DSO9254A:2.5 GHz

KEYSIGHT是德DSO9254A示波器&#xff0c;Infiniium 9000 系列 2.5 GHz 示波器提供 4 个模拟通道、10 Mpts 存储器和 20 GSa/s 采样率。 简介 Keysight(原Agilent) Infiniium DSO9254A 配有 15 英寸 XGA 显示屏&#xff0c;而且包装非常轻巧&#xff0c;仅有 9 英寸深、26 磅重…