0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

glibc导致的堆外内存泄露的排查过程

OSC开源社区 来源:阿里云开发者 2023-09-01 09:43 次阅读

阿里妹导读

本文记录一次glibc导致的堆外内存泄露的排查过程。

问题现象

团队核心应用每次发布完之后,内存会逐步占用,不重启或者重新部署就会导致整体内存占用率超过90%。

dcb20a6c-47ef-11ee-97a6-92fbcf53809c.png

发布2天后的内存占用趋势

探索原因一

堆内找到原因

出现这种问题,第一想到的就是集群中随意找一台机器,信手dump一下内存,看看是否有堆内存使用率过高的情况。 dcd53b36-47ef-11ee-97a6-92fbcf53809c.png 内存泄露 dd002152-47ef-11ee-97a6-92fbcf53809c.png 泄露对象占比 发现 占比18.8%

问题解决

是common-division这个包引入的

dd21defa-47ef-11ee-97a6-92fbcf53809c.png 暂时性修复方案

当前加载俄罗斯(RU)国际地址库,改为一个小国家地址库 以色列(IL)

当前业务使用场景在补发场景下会使用,添加打点日志,确保是否还有业务在使用该服务,没人在用的话,直接下掉(后发现,确还有业务在用呢 )。

完美解决问题,要的就是速度!!发布 ~~上线!!顺道记录下同一台机器的前后对比。 dd482650-47ef-11ee-97a6-92fbcf53809c.png

发布后短时间内有个内存增长实属正常,后续在做观察。

发布第二天,顺手又dump一下同一台机器的内存 ddd00f98-47ef-11ee-97a6-92fbcf53809c.png 由原来的18.8%4.07%的占比,降低了14%,牛皮!! 傻了眼,内存又飙升到86%~~ 该死的迷之自信!! ddf63baa-47ef-11ee-97a6-92fbcf53809c.png 发布后内存使用率

探索原因二

没办法汇报了~~~ 但是问题还是要去看看为什么会占用这么大的内存空间的~

查看进程内存使用

de43352c-47ef-11ee-97a6-92fbcf53809c.png

java 进程内存使用率 84.9%,RES 6.8G。

查看堆内使用情况

当期机器配置为 4Core 8G,堆最大5G,堆使用为不足3G左右。

deb48d9e-47ef-11ee-97a6-92fbcf53809c.png 使用arthas的dashboard/memory 命令查看当前内存使用情况: def96b12-47ef-11ee-97a6-92fbcf53809c.png 当前堆内+非堆内存加起来,远不足当前RES的使用量。那么是什么地方在占用内存??

开始初步怀疑是『堆外内存泄露』

开启NMT查看内存使用

笔者是预发环境,正式环境开启需谨慎,本功能有5%-10%的性能损失!!!

-XX:NativeMemoryTracking=detail


jcmd pid VM.native_memory
df3a66a8-47ef-11ee-97a6-92fbcf53809c.png 如图有很多内存是Unknown(因为是预发开启,相对占比仍是很高)。

概念 NMT displays “committed” memory, not "resident" (which you get through the ps command). In other words, a memory page can be committed without considering as aresident(until it directly accessed).

rssAnalyzer 内存分析

笔者没有使用,因为本功能与NMT作用类似,暂时没有截图了~ rssAnalyzer(内部工具),可以通过oss在预发/线上下载。

通过NMT查看内存使用,基本确认是堆外内存泄露。 剩下的分析过程就是确认是否堆外泄露,哪里在泄露。

堆外内存分析

查了一堆文档基本思路就是

pmap 查看内存地址/大小分配情况

确认当前JVM使用的内存管理库是哪种

分析是什么地方在用堆外内存。

内存地址/大小分配情况

pmap 查看

pmap -x 2531 | sort -k 3 -n -r
df8c4630-47ef-11ee-97a6-92fbcf53809c.png

剧透: 32位系统中的话,多为1M 64位系统中,多为64M。

strace 追踪

由于系统对内存的申请/释放是很频繁的过程,使用strace的时候,无法阻塞到自己想要查看的条目,推荐使用pmap。

strace -f -e"brk,mmap,munmap" -p 2853 原因: 对 heap 的操作,操 作系统提供了 brk()函数,C 运行时库提供了 sbrk()函数;对 mmap 映射区域的操作,操作系 统提供了 mmap()和 munmap()函数。sbrk(),brk() 或者 mmap() 都可以用来向我们的进程添 加额外的虚拟内存。Glibc 同样是使用这些函数向操作系统申请虚拟内存。

e0196218-47ef-11ee-97a6-92fbcf53809c.png

查看JVM使用内存分配器类型

发现很大量为[anon](匿名地址)的64M内存空间被申请。通过附录参考的一些文档发现很多都提到64M的内存空间问题(glibc内存分配器导致的),抱着试试看的态度,准备看看是否为glibc。

cd /opt/taobao/java/bin
ldd java
e02705d0-47ef-11ee-97a6-92fbcf53809c.png

glibc为什么会有泄露

e05ba592-47ef-11ee-97a6-92fbcf53809c.png 我们当前使用的glibc的版本为2.17。说到这里可能简单需要介绍一下glibc的发展史。

V1.0时代』Doug Lea Malloc 在Linux实现,但是在多线程中,存在多线程竞争同一个分配分配区(arena)的阻塞问题。

V2.0时代』Wolfram Gloger 在 Doug Lea 的基础上改进使得 Glibc 的 malloc 可以支持多线程——ptmalloc。

glibc内存释放机制(可能出现泄露时机)

调用free()时空闲内存块可能放入 pool 中,不一定归还给操作系统。

.收缩堆的条件是当前 free 的块大小加上前后能合并 chunk 的大小大于 64KB、,并且 堆顶的大小达到阈值,才有可能收缩堆,把堆最顶端的空闲内存返回给操作系统。

『V2.0』为了支持多线程,多个线程可以从同一个分配区(arena)中分配内存,ptmalloc 假设线程 A 释放掉一块内存后,线程 B 会申请类似大小的内存,但是 A 释放的内 存跟 B 需要的内存不一定完全相等,可能有一个小的误差,就需要不停地对内存块 作切割和合并。

e069d978-47ef-11ee-97a6-92fbcf53809c.png

为什么是64M

回到前面说的问题,为什么会创建这么多的64M的内存区域。这个跟glibc的内存分配器有关下的,间作介绍。 V2.0版本的glibc内存分配器,将分配区域分配主分配区(main arena)和非主分配区(non main arena)(在v1.0时代,只有一个主分配区,每次进行分配的时候,需要对主分配区进行加锁,2.0支持了多线程,将分配区通过环形链表的方式进行管理),每一个分配区利用互斥锁使线程对于该分配区的访问互斥。

主分配区:可以通过sbrk/mmap进行分配。

非主分配区,只可以通过mmap进行分配。

其中,mmap每次申请内存的大小为HEAP_MAX_SIZE(32 位系统上默认为 1MB,64 位系统默 认为 64MB)。

哪里在泄露

既然知道了存在堆外内存泄露,就要查一下到低是什么地方的内存泄露。参考历史资料,可以使用jemalloc工具进行排查。

配置dump内存工具(jemalloc)

由于系统装载的是glibc,所以可以自己在不升级jdk的情况下编译一个jemalloc。

github下载比较慢,上传到oss,再做下载。


sudo yum install -y git gcc make graphviz 
    wget -P /home/admin/general-aftersales https://xxxx.oss-cn-zhangjiakou.aliyuncs.com/jemalloc-5.3.0.tar.bz2 &&  
    mkdir   /home/admin/general-aftersales/jemalloc && 
    cd  /home/admin/general-aftersales/ && 
    tar -jxcf  jemalloc-5.3.0.tar.bz2 && 
    cd  /home/admin/xxxxx/jemalloc-5.3.0/ && 
    ./configure  --enable-prof && 
    make && 
    sudo make install


export LD_PRELOAD=/usr/local/lib/libjemalloc.so.2  MALLOC_CONF="prof:true,lg_prof_interval:30,lg_prof_sample:17,prof_prefix:/home/admin/general-aftersales/prof_prefix

核心配置

make之后,需要启用prof,否则会出现『: Invalid conf pair: prof:true』类似的关键字

配置环境变量

LD_PRELOAD 挂载本次编译的库

MALLOC_CONF 配置dump内存的时机。

"lg_prof_sample:N",平均每分配出 2^N 个字节 采一次样。当 N = 0 时,意味着每次分配都采样。

"lg_prof_interval:N",分配活动中,每流转 1 « 2^N 个字节,将采样统计数据转储到文件。

重启应用

./appctl restart

监控内存dump文件

如果上述配置成功,会在自己配置的prof_prefix 目录中生成相应的dump文件。 然后将文件转换为svg格式


jeprof --svg /opt/taobao/java/bin/java prof_prefix.36090.9.i9.heap > 36090.svg
然后就可以在浏览器中浏览了 e0838364-47ef-11ee-97a6-92fbcf53809c.png

与参阅文档中结果一致,有通过Java java.util.zip.Inflater调用JNI申请内存,进而导致了内存泄露。

既然找到了哪里存在内存泄露,找到使用的地方就很简单了。

发现元凶

通过arthas 的stack命令查看某个方法的调用栈。

statck java.util.zip.Inflater 

java.util.zip.InflaterInputStream

e120ec44-47ef-11ee-97a6-92fbcf53809c.pnge12c03e0-47ef-11ee-97a6-92fbcf53809c.png 如上源码可以看出,如果使用InflaterInputStream(InputStream in) 来构造对象usesDefaultInflater=true, 否则全部为false; 在流关闭的时候。 e1df5bac-47ef-11ee-97a6-92fbcf53809c.pnge263af92-47ef-11ee-97a6-92fbcf53809c.png

end()是native方法。

只有在『usesDefaultInflater=true』的时候,才会调用free()将内存归尝试归还OS,依据上面的内存释放机制,可能不会归还,进而导致内存泄露。

comp.taobao.pandora.loader.jar.ZipInflaterInputStream

二方包扫描

e298f0c6-47ef-11ee-97a6-92fbcf53809c.png ZipInflaterInputStream 的流关闭使用的是父类java.util.zip.InflaterInputStream,构造器使用public InflaterInputStream(InputStream in, Inflater inf, int size) 这样如上『usesDefaultInflater=false』,在关闭流的时候,不会调用end()方法,导致内存泄露。 com.taobao.pandora.loader.jar.ZipInflaterInputStream 源自pandora ,咨询了相关负责人之后,发现2年前就已经修复此内存泄露问题了。

最低版本要求 sar包里的 pandora 版本,要大于等于 2.1.17

问题解决

升级ajdk版本

需要咨询一下jdk团队的同学,需要使用jemalloc作为内存分配器的版本。

升级pandora版本

如上所说,版本高于2.1.17即可。

我们是团队是统一做的基础镜像,找相关的同学做了dockerfile from的升级。

发布部署&观察

e2b4e18c-47ef-11ee-97a6-92fbcf53809c.pnge2dcdd0e-47ef-11ee-97a6-92fbcf53809c.pnge3003aba-47ef-11ee-97a6-92fbcf53809c.png 这此真的舒服了~  

总结

探究了glibc的工作原理之后,发现相比jemalloc的内存使用上确实存在高碎片率的问题,但是本次问题的根本还是在应用层面没有正确的关闭流加剧的堆外内存的泄露。

总结的过程,也是学习的过程,上述分析过程欢迎评论探讨。

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 内存
    +关注

    关注

    8

    文章

    3016

    浏览量

    73989
  • Glibc
    +关注

    关注

    0

    文章

    9

    浏览量

    7496
  • 内存泄露
    +关注

    关注

    0

    文章

    6

    浏览量

    1983

原文标题:实战总结|记一次glibc导致的堆外内存泄露

文章出处:【微信号:OSC开源社区,微信公众号:OSC开源社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    波特率漂移导致通信异常的故障排查过程

    示波器的协议解码功能大家都不生疏,你是否有过波形看起来正常,协议参数、解码设置都正确,却无法正常解码的经历呢?本文以UART协议为例,分享由于波特率漂移导致通信异常的故障排查过程
    的头像 发表于 01-08 13:51 6398次阅读
    波特率漂移<b class='flag-5'>导致</b>通信异常的故障<b class='flag-5'>排查过程</b>

    Linux上对进程进行内存分析和内存泄漏定位

    内存申请,内存管理器使用brk系统调用扩展顶指针。M_MMAP_MAX是该进程中最多使用mmap分配地址段的数量。如果在实际的调试过程中,怀疑某处发生了
    发表于 07-09 08:15

    分享一种内存泄漏定位排查技巧

    常见的泄漏方式在嵌入式开发中,经常会使用malloc,free分配释放内存,稍不小心就可能导致内存一点点地泄露,直至
    发表于 12-17 08:13

    如何有效地排查内存泄露的疑难问题

    不太轻易去改动里面的代码,这无疑也增大了排查难度。在这样的背景下,要完成从“复杂度”如此高的代码里面找出可能出现【内存泄露】的几行问题代码,需要有点手段才行。3 解决思路根据多年对【
    发表于 09-01 14:47

    单片机C语言几种内存泄露总结

    程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak. 这是最常见的内存泄露
    发表于 11-14 10:09 2566次阅读
    单片机C语言几种<b class='flag-5'>内存</b><b class='flag-5'>泄露</b>总结

    Java开发者必须了解的内存技术

    先来看一个 Demo:在 Demo 中分配内存用的是 allocateDirect 方法,但其内部调用的是 DirectByteBuffer,换言之,DirectByteBuffer 才是实际操作
    发表于 07-01 10:19 3781次阅读
    Java开发者必须了解的<b class='flag-5'>堆</b><b class='flag-5'>外</b><b class='flag-5'>内存</b>技术

    DC-DC电源故障排查过程和总结,珍贵的经验!资料下载

    电子发烧友网为你提供DC-DC电源故障排查过程和总结,珍贵的经验!资料下载的电子资料下载,更有其他相关的电路图、源代码、课件教程、中文资料、英文资料、参考设计、用户指南、解决方案等资料,希望可以帮助到广大的电子工程师们。
    发表于 04-25 08:54 75次下载
    DC-DC电源故障<b class='flag-5'>排查过程</b>和总结,珍贵的经验!资料下载

    glibc内存管理存在的共性问题及解决方法

    引言 对于嵌入式设备来说,用户态内存管理是一项基础功能,目前主流的用户态内存管理库有glibc、uclibc、tcmalloc、jemalloc等。 本文基于glibc2.17版本进行
    的头像 发表于 06-18 14:50 3230次阅读

    什么是内存内存是如何分配的?

    在一般的编译系统中,内存的分配方向和栈内存是相反的。当栈内存从高地址向低地址增长的时候,内存
    的头像 发表于 07-05 17:58 9964次阅读

    Glibc内存管理之Ptmalloc2源代码分析

    Glibc内存管理之Ptmalloc2源代码分析
    发表于 07-29 09:20 24次下载

    Java内部类持有外部类导致内存泄露的原因以及其解决方案

    简介 为什么要持有外部类 实例:持有外部类 实例:不持有外部类 实例:内存泄露 不会内存泄露的方案 简介 「说明」 本文介绍 Java 内部类持有外部类
    的头像 发表于 10-08 16:32 970次阅读

    mtrace分析内存泄露

    一、mtrace分析内存泄露 mtrace(memory trace),是 GNU Glibc 自带的内存问题检测工具,它可以用来协助定位内存
    的头像 发表于 11-13 10:55 1270次阅读
    mtrace分析<b class='flag-5'>内存</b><b class='flag-5'>泄露</b>

    内存是如何泄露

    作为 C++ 程序员,内存泄露始终是悬在头上的一颗炸弹。在过去几年的 C++ 开发过程中,由于我们采用了一些技术,我们的程序发生内存泄露的情
    的头像 发表于 11-13 14:13 416次阅读
    <b class='flag-5'>内存</b>是如何<b class='flag-5'>泄露</b>的

    java内存溢出排查方法

    Java内存溢出(Memory overflow)是指Java虚拟机(JVM)中的内存无法满足对象分配的需求,导致程序抛出OutOfMemoryError异常。
    的头像 发表于 11-23 14:46 3207次阅读

    Java怎么排查oom异常

    据量的应用中。要排查OOM异常,需要经过以下几个步骤: 理解OOM异常的原因:OOM异常通常有以下几个原因:内存泄露内存溢出、内存不足以容
    的头像 发表于 12-05 13:47 1232次阅读