一次疑似 JVM native 内存泄漏的排查实录

最近开发同学反馈,某定时任务服务疑似有内存泄漏,整个进程的内存占用比 Xmx 内存大不少,而且看起来是缓慢上升的,做了下面这次分析,包括下面的内容:

  • 分析 JVM native 内存的一些常见思路
  • 内存增长了,怎么甄别是不是内存泄漏
  • 一个完全不熟悉的项目如何找到可能导致 native 内存分配的代码
  • 经典的 Linux 64M 内存问题
  • 到底是内存碎片还是内存泄漏

现象

这个定时任务的应用设置 Xmx 为 925M,但是 native 内存缓存持续增长,但是增长到一定阶段也会保持稳定,不再继续增长。

是内存泄漏吗?

不管是不是内存泄漏,首先要搞清楚的是这段增长的内存是什么,土方法就是用 pmap -x 持续观察内存地址空间的变化。

经过几个小时的 pmap 后台运行,很快发现堆内存几乎无变化,增长的区域都在 64M 内存空间,这就是经典的 glibc 内存分配 64M 问题。

关于 Linux 64M 内存问题,我之前写过几篇相关的文章,大家感兴趣可以去看。

从这里基本可以确定是 native 带来的问题,接下来就是 dump 出来看里面到底存了什么。这里有几个方法

  • 使用 gdb
  • 写一个脚本读取 /proc/<pid>/mem
  • 我自己用 Go 写的一个小工具(可能过段时间释放出来)

脚本内容如下:

cat /proc/$1/maps | grep -Fv ".so" | grep " 0 " | awk '{print $1}' | grep $2 | ( IFS="-"
while read a b; do
dd if=/proc/$1/mem bs=$( getconf PAGESIZE ) iflag=skip_bytes,count_bytes \
skip=$(( 0x$a )) count=$(( 0x$b - 0x$a )) of="$1_mem_$a.bin"
done )
复制代码

执行这个脚本,传入进程号和起始地址就可以把对应内存 dump 到文件中。接下来可以通过 strings 初步查看文件里面有没有认识的字符串。通过 strings 发现很多 jar 包文件里的内容,部分内容如下:

这个内容是项目依赖 jar 包 HikariCP-2.5.1.jar 的 MANIFEST.MF 文件的内容

.
├── MANIFEST.MF
└── maven└── com.zaxxer└── HikariCP├── pom.properties└── pom.xml
复制代码

看来就是程序就是读了 HikariCP-2.5.1.jar 的内容,通过 16 进制分析可以进一步确认。众所周知 jar 包就是一个 zip,如果读取了 zip,那理论内存中会有 zip 的魔数,问一下 ChatGPT zip 的魔数是多少。

用 010 Editor 拿着 50 4B 03 04 去内存里搜,可以看到这个 1M 多的内存文件里有 15 个 zip 魔数。

可以进一步把这个文件当做 zip 文件来解析,可以看到 zip 文件对应的 zip entry 有哪些。

接下来就是去找是谁在读这些 jar 包,读文件会有系统调用,于是这里 strace 就可以看看到底是怎么读的。(也可以通过 jstack 看 java 层的堆栈找到同样的原因,这里不展开)

这里出现了一个不认识的临时文件,还有一个前缀 FastClasspathScanner,去代码里搜,原理是项目用了 FastClasspathScanner 来扫描 class 文件

FastClasspathScanner 项目地址在 github.com/classgraph/… ,FastClasspathScanner 提供了一种简单快速的方法来扫描 Java 类路径。它可以轻松找到类路径上的所有类、资源、包和模块,并获取有关它们的信息。这个项目用它来做什么呢?

经过看代码,它大概是用来去 jar 包里搜哪些类实现了
com.seewo.school.statistics.counter.Counter 接口,然后去 classpath 中的找到实现了这个接口的类,也就是遍历所有的 jar 包去找实现类。

FastClasspathScanner 的做法是先把这些依赖的 jar 包先拷贝到临时目录(注意这里的 tempFile.deleteOnExit(),虽然跟此次问题不相关,但也是一个内存隐患,等下介绍)

然后读取这些临时 jar 包,

大量申请释放内存的地方在 java.util.zip.Inflater 类,调用它的 end 方法会释放 native 的内存。如果 end 方法没有调用,就会导致内存泄漏,
java.util.zip.InflaterInputStream 类的 close 方法在一些场景下是不会调用 Inflater.end 方法,如下所示。

但是 Inflater 类有实现 finalize 方法,在 Inflater 对象不可达以后,JVM 会帮忙调用 Inflater 类的 finalize 方法

public class Inflater {public void end() {synchronized (zsRef) {long addr = zsRef.address();zsRef.clear();if (addr != 0) {end(addr);buf = null;}}}protected void finalize() {end();}private native static void initIDs();// ...private native static void end(long addr);
}
复制代码

有几种可能性

  • Inflater 因为被其它对象引用,没能释放,导致 finalize 方法不能被调用,内存自然没法释放
  • Inflater 因为还没被 FinalizerThread 执行 fianlize 方法,导致没有释放
  • Inflater 的 finalize 方法被调用,但是被 libc 的 ptmalloc 缓存,没能真正释放回操作系统

更多关于 finalize 机制,大家可以移步笨神的文章:「JVM源码分析之警惕存在内存泄漏风险的FinalReference(增强版) 」 heapdump.cn/article/265…

于是 dump 堆内存去分析是不是有大量的 Inflater 类没有被回收,经过内存分析看,发现 java.util.zip.Inflater 类有 6k 多没有被回收。

没有被回收的原因是它们被 Finalizer 引用,需要两次 GC 才有可能被回收。

而且 FinalizerThread 的优先级比较低,如果 CPU 比较紧张的情况下,会导致需要很久才会把队列中 f 对象的 finalize 方法执行完。又因为这个时间比较长,可能导致 f 对象多次 GC 以后进到老年代,如果老年代 gc 频率不高,那 f 对象存活的时间就更久了。

这样的 native 内存短时间不释放,又由于定时任务长期执行,就可能会导致内存碎片、glibc 内存不归还的出现(等下验证),就算释放 libc 也有可能不会还给操作系统。

通过手动多次触发 GC,确认可以将所有的 java.util.zip.Inflater 回收掉,但是 natvie 内存并没有太大的变化。于是怀疑是 glibc 的内存碎片和内存没有归还给操作系统。

如何修改

有几种可能的修改方式

方案 1:其实这里明显是程序上设计不合理,没必要每次定时任务都去扫描包,这些包又不会变,扫描一次就可以了,让开发的同学去修改代码,把第一次扫描的结果缓存起来。然后打了一个包去开发环境运行,效果非常明显,新版本跑了一整天都内存几乎没有什么波动,旧版本则缓慢的上涨了 400M 左右。

方案 2:修改 FastClasspathScanner 代码,在流关闭的时候,顺带关闭 Inflater, SpringBoot 里面是这么实现的。(不想改了)

SpringBoot 里面的改动如下:github.com/spring-proj…

方案 3:前面怀疑是因为 glibc 的内存碎片,尝试替换碎片整理更友好的 tcmalloc 或者 jemalloc,看看效果。

LD_PRELOAD=/usr/local/lib/libtcmalloc.so java -jar xxx
复制代码

下面是换了 tcmalloc 以后的效果,tcmalloc 贼稳。

可以看到换到了对内存碎片更友好的内存分配器以后,内存的增长得到了非常好的控制。

番外篇

上面提到 tempFile.deleteOnExit() 会有巨大的坑,通过内存 dump 的分析,可以看到 java.io.DeleteOnExitHook 占了将近 40M。

里面有一个静态的 hashset,里面存了 10 几万个字符串,就是 FastClasspathScanner 产生的临时文件路径。

是因为这里调用了 File.deleteOnExit,这个可太坑了。

它把文件的路径加到了一个 jvm 全局 DeleteOnExitHook 类的静态变量 files 中。

又因为临时文件每次的路径都是不一样的,导致这个 hashset 随着定时任务的执行逐渐变大,永远无法回收。

DeleteOnExitHook 本意是用来在 Java 虚拟机退出的时候删除文件。

对于 server 端这种长时间运行的程序,用 deleteOnExit 就太坑了,只有等容器退出那会才会执行删除。再加上这里的文件路径每次都变,导致内存白白浪费。

小结

因为程序设计的问题导致频繁读取 jar 包(实际是 zip 文件),需要调用 native 的代码去处理 zip 文件,会有非常多 native 内存分配的产生。又因为用了 zip 默认的 InflaterInputStream,导致没有办法在流关闭时调用 java.util.zip.Inflater 类的 end 方法释放 native 内存,只能等到 Finalizer 机制在多次 GC 以后调用,导致了 native 内存可能在短时间内无法释放。

又因为内存碎片和 libc 内存分配器的实现策略,导致了它没有将内存真正释放给操作系统,导致了缓慢的内存增长。

简单来说,有一个猪队友在不停的申请内存(无法立刻释放),又由于 libc 碎片化和内存二道贩子不一定会把 native 内存还给 os,导致了内存的缓慢增长。

一点想法:

  • Java 的 zip 机制是真的设计有点坑,
  • Finalize 机制完全帮倒忙,弊远大于利,新版本 Java 确实也做了修改。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【618期间】超过200小时的课程全都有优惠,全年最好的加入有三AI学习的时间来了~...

正值2023年618期间&#xff0c;既然是全民购物节&#xff0c;有三AI所有付费的视频课程开启优惠活动&#xff0c;即日起至节日结束&#xff08;6月18日晚23:59&#xff09;。 当前已有课程包括数据使用/模型分析/图像分类/图像分割/目标检测/图像生成/图像翻译/图像增强/视频分…

虚假新闻检测概述

几个概念 社交网络的新闻往往包括新闻内容&#xff0c;社交上下文内容&#xff0c;以及外部知识。其中新闻内容指的是文章中所包含的文本信息以及图片视频等多模态信息。社交上下文信息指的是新闻的发布者&#xff0c;新闻的传播网络&#xff0c;以及其他用户对新闻的评论和转发…

认识ChatGPT

ai是由dutuai训练的一种大型自然语言处理模型&#xff0c;能够进行自然语言对话。它基于预训练的语言模型gpt&#xff08;generative pre-trained transformer&#xff09;&#xff0c;具有强大的自然语言理解和生成能力。ai可以通过了解上下文并推断回应来与用户进行交互。它被…

ChatGPT之后何去何从?LeCun新作:全面综述下一代「增强语言模型」

来自&#xff1a;新智元 【导读】语言模型该怎么增强&#xff1f; ChatGPT算是点燃了语言模型的一把火&#xff0c;NLP的从业者都在反思与总结未来的研究方向。 最近图灵奖得主Yann LeCun参与撰写了一篇关于「增强语言模型」的综述&#xff0c;回顾了语言模型与推理技能和使用工…

数据库mysql

目录 数据库的实用性 操作网上商城数据库系统 维护数据库的完整性&#xff08;过&#xff09; 维护数据库的完整性是确保数据库数据的正确性和一致性的关键。以下是一些常见的方法来维护数据库的完整性&#xff1a; 添加修改和删除数据 查询网上商城系统数据 选择列 排…

reggie

分页 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width, initia…

Chat GPT是什么?初学怎么使用Chat GPT?

1.Chat GPT介绍 ChatGPT的全称是"Chat Generative Pre-training Transformer"&#xff0c;中文意思是“对话生成预训练变形器”。它是一种基于预训练的自然语言处理模型&#xff0c;旨在实现智能对话生成和理解。通过在大量文本数据上进行预训练&#xff0c;ChatGPT可…

AIGC for code(AIGC/AI生成代码/生成式AI之代码生成/AI编程工具/自动编程/自动生成代码/智能编程工具/智能编程系统)

AIGC&#xff0c;Artificial Intelligence Generated Content&#xff0c;人工智能生成内容 AIGC for code&#xff0c;AI生成代码 1 Github Copilot 1.1 简介 Copilot是由微软的子公司Github与openAI共同开发的人工智能&#xff08;AI&#xff09;驱动的编程助手。它能够直…

Windows下搭建局域网内简易git服务器

这里写自定义目录标题 概述配置步骤1.任意位置创建git 仓库2.启动Git Daemon3.其他电脑克隆工程4.开机自动启动5.其他配置注意事项 概述 由于和朋友小规模制作项目&#xff0c;又使用了UE5这样的庞然大物&#xff0c;准备整一个本地轻量化一些git版本管理。 查阅资料是发现git…

在Oracle Linux上部署Yunzai Bot v3保姆式教程/甲骨文云/云崽Bot/原神

去我的博客查看本文&#xff1a;在Oracle Linux上部署Yunzai Bot v3保姆式教程 – 肚 (iocky.com) 本文也在Github与gitee可用。 初始配置 直接注册最低配置的就ok了&#xff0c;这里不再赘述如何注册Oracle Cloud以及开设Compute Instance。 先点进目标实例&#xff0c;然后点…

保姆级教程:Linux和Windows下本地化部署Vicuna模型

目录 文章摘要一、Vicuna简介1. Vicuna模型定义2. Vicuna模型的应用场景3. Vicuna模型的训练数据4. Vicuna模型的版本5. 性能评估 二、linux 操作系统下部署1. 环境介绍2. 安装Python3.10.72.1 下载Python3.10.7安装包2.2 安装gcc编译器2.3 安装依赖包2.4 升级openssl版本2.4.1…

LLMs 诸神之战:LangChain ,以【奥德赛】之名

LLMs 一出&#xff0c;谁与争锋&#xff1f; 毫无疑问&#xff0c;大语言模型&#xff08;LLM&#xff09;掀起了新一轮的技术浪潮&#xff0c;成为全球各科技公司争相布局的领域。诚然&#xff0c;技术浪潮源起于 ChatGPT&#xff0c;不过要提及 LLMs 的技术发展的高潮&#x…

chatgpt赋能python:Python游戏辅助教程:让你的游戏更加容易

Python游戏辅助教程&#xff1a;让你的游戏更加容易 介绍 Python是一种非常受欢迎的编程语言&#xff0c;具有灵活性和易用性。Python可用于编写各种类型的程序&#xff0c;包括游戏辅助工具。Python的易用性和维护性&#xff0c;使得它成为游戏玩家、开发人员和测试人员的首…

Oracle账户被锁定解决方法

当用PLSQL登录Oracle时提示ORA-28000: the account is locked&#xff1b; 这个提示就是当前用户被锁定&#xff1b; 为什么会被锁定呢&#xff1f; 用户登录十次没有成功的&#xff0c;当前用户会被锁定&#xff1b;安装时没有解锁的&#xff1b; 下面我们用两种比较常用的方…

chattr、lsattr目录锁定解锁与查看

创建一个目录&#xff0c;并在目录中创建一个文件夹和文件 [rootk8s-m-01 ~]# mkdir /aaa/ [rootk8s-m-01 ~]# cd /aaa/ [rootk8s-m-01 aaa]# mkdir bbb [rootk8s-m-01 aaa]# touch ccc [rootk8s-m-01 aaa]# ls bbb ccc 使用chattr对/aaa/目录下所有文件进行锁定可以发现锁定…

python编程获取《续蜀山剑侠传》目录信息:目录名称和网址

一直很欣赏武侠小说宗师还珠楼主李寿民的扛鼎之作《蜀山剑侠传》&#xff0c;可惜由于种种原因&#xff0c;《蜀山剑侠传》并未写完。这着实令还珠迷们扼腕&#xff0c;也有不少人继写了《蜀山剑侠传》&#xff0c;但是良莠夹杂&#xff0c;其中有一位退休公务员写的《续蜀山剑…

chatgpt赋能Python-python_nah

Python Nah&#xff1a;一场涵盖搜索引擎的革命 一、Python Nah的简介 Python Nah是一种基于Python编程语言的搜索引擎技术&#xff0c;旨在改进搜索引擎的性能和搜索结果的准确性。与其他搜索引擎技术不同&#xff0c;Python Nah利用了Python的机器学习优势&#xff0c;并且…

可汗学院统计学1-16课笔记

[第1课] 均值 中位数 众数 均值(平均值) 数据之和 / 数据个数中位数 数据排序后&#xff0c;处在中间的数&#xff08;如果两位数取平均值&#xff09;众数 出现次数最多的数,一组数据可以有多个众数 import numpy as np import pandas as pd#定义数据 datanp.array([1,2,…

余承东回应高通对华为恢复 5G 芯片供应;ChatGPT 发布重要更新;微软推出免费 AI 入门课|极客头条...

「极客头条」—— 技术人员的新闻圈&#xff01; CSDN 的读者朋友们早上好哇&#xff0c;「极客头条」来啦&#xff0c;快来看今天都有哪些值得我们技术人关注的重要新闻吧。 整理 | 梦依丹 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 一分钟速览新闻点&#…

23.4.25 Go学习日记

1. Go的命名规范&#xff08;生成自ChatGPT&#xff09; 1.1 包名 包名应该小写并尽可能用单个简短的词组&#xff0c;不要使用下划线或混合大小写。 1.2 文件名 Go 语言的文件名通常为小写字母&#xff0c;可以包含下划线 (_) 或点 (.)&#xff0c;但不建议&#xff0c;并确…