当前位置: 首页 > news >正文

堆外内存泄露排查经历

优质博文:IT-BLOG-CN

一、问题描述

淘宝后台应用从今年某个时间开始docker oom的量突然变多,确定为堆外内存泄露。

后面继续按照上一篇对外内存分析方法的进行排查(jemallocpmapmalloc+pmap/maps+NMT+jstack+gdb),但都没有定位到问题。至于为什么没有定位到问题,后面会根据问题的特点进行分析。

至此,回到原点。其实也不是原点,最起码已经确定了是堆外内存off-heap,而不是Native Memory(JVM自身所用内存)泄露。所以你看,很多时候问题排查其实是排除法(苦涩的笑)。

二、排查过程

堆外内存off-heap的泄露,不外乎有以下几个原因:
【1】流没有关闭;
【2】Unsafe.allocateMemory内存没释放;
【3】jni内存没有释放;

其中流没有关闭是最常见的,而23出现的概率是比较低的,所以先排查流没有关闭的可能。

2.1 走寻常路

对于流没有关闭导致泄露的定位,一般来说有以下4种方式:
【1】看代码;
【2】用jemalloc分析;
【3】分析堆内存找小尾巴;

2.1.1 看代码

流未关闭的话,一般来说都是因为没有显式地调用close()方法或没有使用try-with-resource的方式管理流。比如下面这段代码就存在流未关闭的情形:
在这里插入图片描述

像下面的这段代码,流会被try-with-resource机制去关闭,正常情况下不会出现内存泄露。而存在泄露的应用,恰恰就是用try-with-resource机制去管理流,所以排除这里的嫌疑。
在这里插入图片描述

2.1.2 用jemalloc分析

这里用到jemalloc主要是利用它的heap dump以及它的jeprof命令来分析java进程的内存分配情况。注意这里的heap不是jvm堆内存,而是操作系统视角的内存布局,比如heapstackBSS、数据段、代码段,这里不是本文的重点,就不展开描述了……

使用jemalloc分析内存分配的过程很多文章都有描述,这里也不展开了,结果是通过jeprof生成的pdf文件,依然没有发现导致流未关闭的场景,只能作罢。

2.1.3 分析堆内存

通过看代码的方式以及jemalloc都没法定位到流未关闭的情形,考虑代码走查难免有遗漏,同时应用使用了大量的第三方组件,第三方组件会不会存在流未关闭的可能呢?但很显然,如果去分析第三方组件的代码会累吐血。联想到流没有关闭的情形,一般会在堆内存里面留一些引用的痕迹,于是开始dump java堆内存。

内存dump下来后,通过MAT查找java.lang.ref.FinalizerInputStreamOutputStream相关的对象,依然一无所获,这时开始怀疑内存的泄露跟流未关闭没有关系。

2.1.4 内部工具分析

公司内部提供了一个跟踪内存分配的工具,通过扩展malloc方法获取到分配内存的调用线程和内存地址,通过jstack打印线程栈,结合gdbpmap等方式获取可疑内存段,以定位内存泄露源头。通过这种方式依然没有找到任何线索,同时jstack的方式会导致应用出现短暂的停顿safepoint而影响性能,所以这种方式也放弃了。

2.2 走了弯路

在暂时排除了流未关闭的嫌疑后,这时转向分析直接用Unsafe.allocateMemory分配的内存。有些组件不会基于java.nio.DirectByteBuffer(int cap)申请堆外内存,而是直接用unsafe.allocateMemory方法申请内存,这时候MaxDirectMemorySize是限制不住堆外内存的用量的,当然基于DirectByteBuffer申请的堆外内存,最终也是基于unsafe.allocateMemory方法申请内存,所以这里只要分析unsafe.allocateMemory申请的内存即可。到这里,前面提到的神器async-profiler就粉墨登场了。

async-profiler的安装步骤这里就不介绍了,可以自行安装。安装完毕使用以下脚本就可以分析Unsafe_AllocateMemory0的内存分配情况了。

sudo -u deploy /tmp/async-profiler-2.9-linux-x64/profiler.sh -e Unsafe_AllocateMemory0 -d 1200 -f /tmp/unsafe_alloc-$(pgrep java)-$(date +'%y%m%d%H%M').html $(pgrep java) 

这里-e代表要分析的事件,-d代表分析的时长,以秒为单位。生成的结果是一张火焰图,你可以下载下来在浏览器上查看哪块用到了Unsafe_AllocateMemory0来分配内存。

比较悲催的是,通过Unsafe_AllocateMemory0分配的内存比较少,所以这里的嫌疑也被排除了。所以分析Unsafe_AllocateMemory0这一步算是走了弯路。

2.3 柳暗花明

前面所有的手段都用尽之后,已经快一个星期过去了。在前面的手段都用尽之后,尝试分析jni的内存分配情况。其实这时候有点死马当作活马医的味道了。

jni(Java Native Interface),简单说就是Java调用c/c++写的程序,实现更强的功能。c写的程序,要分配内存,一般是通过malloc()方法向操作系统申请内存。在malloc的实现中,一般分配大块内存 128KB会使用mmap分配内存空间。而async-profiler可以通过分析linux perf_event中的perf_event_mmap_page来追踪内存分配情况的。想到这里,便尝试通过下面的命令来追踪系统层面malloc情况:

sudo -u deploy /tmp/async-profiler-2.9-linux-x64/profiler.sh --loop 1h -e malloc -f /tmp/malloc-$(pgrep java)-%t.html $(pgrep java) 

这个命令中的–loop参数是能够以1个小时间隔不间断跟踪内存分配情况,如果你想长时间进行问题定位,可以尝试使用一下这个参数,profiler会每隔1个小时生成一个html文件,是不是很方便?

-e malloc就是告诉async-profiler去追踪perf_event_mmap_page的内存分配。

运行了1个小时后,就得到了下面的这个内存分配火焰图:
在这里插入图片描述

从图中可以看出,zstd-jni这个组件分配了大量的内存。因为在之前我们通过review代码排查流没有关闭的场景时,是看过这段代码的,但当时没有发现什么问题。但从火焰图中看到分配的内存量,总感觉不对劲。这时候忽然想到,能不能从日志中找到什么蛛丝马迹呢?于是开始扒日志,这时,一个broken pipe的异常引起了我的注意:
在这里插入图片描述

这种broken pipe的异常其实蛮常见的,尤其在有一方断开连接时,很容易就出现这种异常。但顺着调用栈往下看,顿时眼前一亮,其中有ZstdOutputStream的调用。流里面的异常那是很容易泄露的,于是进入到ZstdOutputStream.java 178行看代码,发现了zstd-jni 1.3.x版本存在的bug:当ZstdOutputStream关闭流的时候,会尝试把剩余的数据发送出去。但这时候如果连接已经关闭了,它就咯咯了,导致流关闭不掉,jni的内存也释放不掉。
在这里插入图片描述

这个bug,在1.4.4-11版本中就修复了,我们可以看到作者用try-finally捕获了out.write的异常,这样不管zstd依赖的流的状态如何,它最终都会释放自己使用的资源。
在这里插入图片描述

定位到问题之后就好办多了,将zstd-jni的版本升级到1.4.9-5之后的版本,这个问题就不存在了,下面是修复后RSS的情况,可以看到RSS很平稳了:
在这里插入图片描述

三、总结

这个case从开始排查到最终定位到问题,花费了一个星期的时间,成本巨大,回过头看看排查的步骤,貌似也没什么问题,但终究是走了一些弯路:

3.1 忽略了异常信息

如果最开始就重视异常信息的话,那么这个问题可能很早就定位到了。但这个应用自己不是直接责任人,而且在看到broken pipe的时候犯了经验性错误,没有往影响流关闭的角度想,导致方向错误,浪费了大量的时间。

所以,系统中任何的异常,都要重视起来,避免产生更严重的问题。

3.2 jemalloc失效

jemalloc在分析内存持续泄露方面比较方便,但对于非稳定复现的场景,如果采样间隔过久,有可能会导致错过问题点。而如果你将采样间隔调短,又会造成生成大量的dump文件,在用jeprof生成分析报告的时候,可能会导致too many arguments的错误而无法生成分析报告。

3.3 内部工具失效

内部工具,能够把可疑的内存段内容用strings命令查看,某些场景是能够发现蛛丝马迹的,为什么这个case就不行了呢?这里猜测是因为zstd对数据做了压缩,用strings看到的全是乱码,没法发现数据的特征;

综上,问题排查很多时候真的像排雷一样,一个个的去排除。这需要的是耐心和毅力,当你最终定位到问题的时候,那种如释重负的感觉会让自己觉得一切都是值得的。

相关文章:

堆外内存泄露排查经历

优质博文:IT-BLOG-CN 一、问题描述 淘宝后台应用从今年某个时间开始docker oom的量突然变多,确定为堆外内存泄露。 后面继续按照上一篇对外内存分析方法的进行排查(jemalloc、pmap、mallocpmap/mapsNMTjstackgdb),但都没有定位到问题。至于…...

SpringBoot Task

相关文章链接 定时任务工具类(Cron Util)SpringBoot Task 参数详解 Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) Retention(RetentionPolicy.RUNTIME) Documented Repeatable(Schedules.class) public interface Scheduled {String C…...

学习路之压力测试--jmeter安装教程

Jmeter安装 0、先安装jdk:这里是安装jdk-8u211-windows-x64 1、百度网盘上下载 jdk和jmeter 链接: https://pan.baidu.com/s/1qqqaQdNj1ABT1PnH4hfeCw?pwdkwrr 提取码: kwrr 复制这段内容后打开百度网盘手机App,操作更方便哦 官网:Apache JMeter - D…...

大模型部署,运维,测试所需掌握的知识点

python环境部署: python3 -m site --user-base 返回用户级别的Python安装基础目录 sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 1 将python3的默认路径/usr/bin/python3替…...

ECharts柱状图-带圆角的堆积柱状图,附视频讲解与代码下载

引言: 在数据可视化的世界里,ECharts凭借其丰富的图表类型和强大的配置能力,成为了众多开发者的首选。今天,我将带大家一起实现一个柱状图图表,通过该图表我们可以直观地展示和分析数据。此外,我还将提供…...

java 并发编程 (2)Thread 类和 Runnable 接口详解

目录 1. Thread 类和 Runnable 接口的设计目的 1.1 为什么有 Thread 类和 Runnable 接口? 2. Thread 类实现的详细分析 2.1 Thread 类的构造方法 2.2 start() 方法的工作原理 2.3 run() 方法 2.4 join() 方法 3. Runnable 接口的实现和作用 3.1 Runnable 接…...

人工智能之数学基础:线性代数在人工智能中的地位

本文重点 从本文开始,我们将开启线性代数的学习,在线性代数中有向量、矩阵,以及各种性质,那么这些数学知识究竟和人工智能有什么关系呢? 重要性 机器学习和深度学习的本质就是训练模型,要想训练模型需要使…...

PostgreSQL WITH 子句:提高查询效率和可读性

PostgreSQL WITH 子句:提高查询效率和可读性 PostgreSQL 是一种功能强大的开源关系数据库管理系统,它以其稳定性、可靠性和高级功能而闻名。在 PostgreSQL 中,WITH 子句(也称为公用表表达式,CTE)是一种非常有用的特性,它允许用户在一个大的查询中创建一个临时的结果集,…...

TransFormer--解码器:前馈网络层、叠加和归一组件

TransFormer--解码器:前馈网络层、叠加和归一组件 解码器的下一个子层是前馈网络层,如下图所示。 解码器的前馈网络层的工作原理与我们在编码器中学到的完全相同 叠加和归一组件 和在编码器部分学到的一样,叠加和归一组件连接子层的输入和输…...

2024亚太杯国际赛C题参考文章50页+完整解题思路+数据处理+最终结果

中国宠物食品行业的发展趋势与汇率情景分析:基于多模型的量化预测与决策分析 一 、 摘要 本文针对宠物产业及相关产业的发展分析问题,采用多种数学建模方法和数据 分析技术,构建了一系列预测和评估模型。从宠物数量预测、全球市场分析、产业 …...

Kafka 分区分配及再平衡策略深度解析与消费者事务和数据积压的简单介绍

Kafka:分布式消息系统的核心原理与安装部署-CSDN博客 自定义 Kafka 脚本 kf-use.sh 的解析与功能与应用示例-CSDN博客 Kafka 生产者全面解析:从基础原理到高级实践-CSDN博客 Kafka 生产者优化与数据处理经验-CSDN博客 Kafka 工作流程解析&#xff1a…...

useEffect、useCallback、useMemo和memo的区别

前言 在构建现代 React 应用时,性能优化是一个关键考虑因素。随着组件的复杂性增加,合理管理状态和副作用变得尤为重要。React 提供了多个工具来帮助开发者优化组件性能,其中最常用的包括 useEffect、useCallback、useMemo 和 React.memo。这…...

layui树形组件点击树节点后高亮的解决方案

效果显示&#xff1a; 代码 //节点高亮var nodes document.getElementsByClassName("layui-tree-txt");for (var i 0; i < nodes.length; i) {if (nodes[i].innerHTML obj.data.title){nodes[i].style.color "#006BF9";nodes[i].style.fontWeight …...

大语言模型(LLM)安全:十大风险、影响和防御措施

一、什么是大语言模型&#xff08;LLM&#xff09;安全&#xff1f; 大语言模型&#xff08;LLM&#xff09;安全侧重于保护大型语言模型免受各种威胁&#xff0c;这些威胁可能会损害其功能、完整性和所处理的数据。这涉及实施措施来保护模型本身、它使用的数据以及支持它的基…...

02 —— Webpack 修改入口和出口

概念 | webpack 中文文档 | webpack中文文档 | webpack中文网 修改入口 webpack.config.js &#xff08;放在项目根目录下&#xff09; module.exports {//entry设置入口起点的文件路径entry: ./path/to/my/entry/file.js, }; 修改出口 webpack.config.js const path r…...

Go语言进阶依赖管理

1. Go语言进阶 1.1 Goroutine package mainimport ("fmt""time" )func hello(i int) {println("hello goroutine : " fmt.Sprint(i)) }func main() {for i : 0; i < 5; i {go func(j int) { hello(j) }(i) // 启动一个新的 goroutine&…...

集成了高性能ARM Cortex-M0+处理器的一款SimpleLink 2.4 GHz无线模块-RF-BM-2340B1

蓝牙模组 - RF-BM-2340B1是基于美国TI的CC2340R5为核心设计的一款SimpleLink 2.4 GHz 无线模块。支持Bluetooth 5.3 Low Energy、Zigbee 、IEEE 802.15.4g、TI 15.4-Stack (2.4 GHz)及私有协议。集成了高性能ARM Cortex-M0处理器&#xff0c;具有512 KB Flash、32 KB超低泄漏SR…...

ffmpeg本地编译不容易发现的问题 — Error:xxxxx not found!

这里区分电脑CPU架构 本次编译是在Mac笔记本&#xff0c;M1芯片上进行&#xff01; 前面大致流程&#xff1a;分为两种&#xff08;1.仅适用&#xff0c;直接下载编译好的本地安装即可&#xff1b;2.使用并查看源码&#xff0c;自己修改编译运行&#xff09;。这里介绍的是第…...

mybatis——Mapper代理方式

一、原始DAO开发问题 Dao接口实现类方法中存在大量模板方法&#xff0c;设想能否将这些代码提取出来&#xff0c;大大减轻程序员的工作 量。 调用sqlSession的数据库操作方法需要指定statement的id&#xff0c;这里存在硬编码&#xff0c;不利于开发维护。 调用SqlSession方…...

FreeRTOS——消息队列

目录 一、概念及其作用 1.1概念 1.2特点 1.3工作原理 二、相关API 2.1创建队列 2.2任务中写队列 2.3任务中读队列 2.4中断中写队列 2.5中断中读队列 三、实现原理 3.1消息队列控制块 3.2消息队列的创建 3.3消息的发送 3.3.1任务中发送 3.3.2中断中发送 3.4消息的…...

【题解】—— LeetCode一周小结46

&#x1f31f;欢迎来到 我的博客 —— 探索技术的无限可能&#xff01; &#x1f31f;博客的简介&#xff08;文章目录&#xff09; 【题解】—— 每日一道题目栏 上接&#xff1a;【题解】—— LeetCode一周小结45 11.切棍子的最小成本 题目链接&#xff1a;1547. 切棍子的最…...

Java项目实战II基于微信小程序的校运会管理系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导 一、前言 在充满活力与激情的校园生活中&#xff0c;校运会不仅是…...

python里的数据结构

列表&#xff08;List&#xff09; 定义和特点&#xff1a; 列表是一种有序的可变序列&#xff0c;可包含不同类型的元素&#xff0c;如整数、字符串、列表等。可以通过索引访问和修改元素&#xff0c;索引从 0 开始。代码示例&#xff1a; my_list [1, 2, apple, [4, 5]] pr…...

[Unity Demo]从零开始制作空洞骑士Hollow Knight第二十一集:制作游戏的金钱系统吉欧Geo和初步制作HUD Canvas的额外内容

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、制作游戏的金钱系统吉欧Geo以及HUD Camera 1.制作金钱系统吉欧2.制作吉欧的脚本Geo Counter逻辑处理3.制作HUD Canvas的吉欧的UI4.在敌人的HealthManager.c…...

底层逻辑之:极大似然方法(Maximum Likelihood Estimation, MLE)

简介&#xff1a; 极大似然方法&#xff08;Maximum Likelihood Estimation, MLE&#xff09;是一种用于估计统计模型参数的方法。其核心思想是基于观测数据来寻找最可能产生这些数据的模型参数。 早在1821年&#xff0c;德国数学家高斯&#xff08;C. F. Gauss&#xff09;就…...

笔记:Centos Nginx Jdk Mysql OpenOffce KkFile Minio安装部署

远程工具 ToDesk Nginx 解压 tar zxvf nginx-1.20.2.tar.gz进入Nginx 文件夹 cd nginx-1.20.2报错解决 ./configure: error: C compiler cc is not found yum -y install gcc gcc-c autoconf automake make./configure: error: the HTTP rewrite module requires the PC…...

【MARL】深入理解多智能体近端策略优化(MAPPO)算法与调参

&#x1f4e2;本篇文章是博主强化学习&#xff08;RL&#xff09;领域学习时&#xff0c;用于个人学习、研究或者欣赏使用&#xff0c;并基于博主对相关等领域的一些理解而记录的学习摘录和笔记&#xff0c;若有不当和侵权之处&#xff0c;指出后将会立即改正&#xff0c;还望谅…...

深入探索Go语言中的sync.Mutex与sync.RWMutex:原理、应用与实践

深入探索Go语言中的sync.Mutex与sync.RWMutex:原理、应用与实践 在并发编程的世界里,Go语言以其独特的并发模型和简洁的语法赢得了广泛的关注。在Go语言的并发控制工具箱中,sync.Mutex和sync.RWMutex是两个至关重要的工具,它们帮助开发者保护共享资源,避免竞态条件,确保…...

15.postgresql--jsonb 数组进行打平,过滤

用jsonb_array_elements函数先展开数组&#xff0c;再用jsonb_each函数遍历元素中的键值对 例如&#xff1a; SELECT * FROM data_table, LATERAL jsonb_array_elements(json_column) WITH ORDINALITY as elem(element, idx) JOIN LATERAL jsonb_each(elem.element) as kv(ke…...

linux下i2c开发与框架源码分析

目录 1 概述 2 I2c子系统框架 3 I2C的使用流程 3.1 在驱动里使用 3.2 在应用层使用 3.3 I2ctool的使用 4 为硬件i2c注册一个适配器 5 i2c子系统源码流程分析 5.1 i2c device与driver绑定过程 5.1.1 Driver的注册与处理 5.1.2 Client device的生成 5.2 I2c的发送与接…...