[转帖]【技术剖析】11. 使用jemalloc解决JVM内存泄露问题

技术,剖析,使用,jemalloc,解决,jvm,内存,泄露,问题 · 浏览次数 : 0

小编点评

**jemalloc 工具的典型使用方法** **1. 生成内存申请代码路径图** ```java jeprof --base=jeprof.34070.0.i0.heap ``` **2. 创建 PDF 文件** ```java jeprof --pdf /home/xxxx/jdk1.8.0_292/bin/java jeprof.34070.1.i1.heap ``` **3. 对两个 dump 文件进行差异对比** ```java diff --git a/autogen.sh b/autogen.shindex ``` **4. 获取内存增长差异图** ```java jeprof --version ``` **5. 设置环境变量并执行 Jeprof 文件** ```bash bin$ chmod +x ./* export PATH=$PATH:$JEMALLOC_DIR/bin ./jeprof xxxx@hostname:jemalloc ``` **其他提示** * 确保运行 Jeprof 文件具有执行权限。 * 复制上述代码到 `bin` 目录的上一层目录中。 * 如果遇到技术问题,可以进入毕昇 JDK 社区查找相关资源。

正文

https://bbs.huaweicloud.com/forum/thread-169523-1-1.html

 

作者:王坤

> 编者按:JVM 发生内存泄漏,如何能快速定位到内存泄漏点并不容易。笔者通过使用 jemalloc(可以替换默认的 glibc 库)中的 profiling 机制(通过对程序的堆空间进行采样收集相关信息),演示了如何快速找到内存泄漏的过程。

Java 的内存对象一般可以分为堆内内存、堆外内存和 Native method 分配的内存,对于前面两种内存,可以通过 JVM 的 GC 进行管理,而 Native method 则不受 GC 管理,很容易引发内存泄露。Native Method 导致的内存泄漏,无法使用 JDK 自带的工具进行分析,需要通过 malloc_hook 追踪 malloc 的调用链帮助分析,一般可以采用内存分配跟踪工具(malloc tracing)协助分析内存泄漏。该工具的使用较复杂:需要修改源码,装载 hook 函数,然后运行修改后的程序,生成特殊的 log 文件,最后利用 mtrace 工具分析日志来确定是否存在内存泄露并分析可能发生内存泄露的代码位置。由于 hotspot 代码量较大,虽然可以通过一些选项逐步缩小可疑代码范围,但是修改代码总不是最优选择。另外,Valgrind 扫描也是一种常用的内存泄露分析工具,虽然 Valgrind 非常强大,但是 Valgrind 会报出很多干扰项,且使用较为麻烦。本文主要是介绍另一种分析 Native method 内存泄漏的方法,供大家参考。

jemalloc 是通过 malloc(3) 实现的一种分配器,代替 glibc 中的 malloc 实现,开发人员通过 jemalloc 的 Profiling 功能分析内存分配过程,可帮助解决一些 Native method 内存泄漏问题。

1 jemalloc 使用方法

jemalloc 使用方法的详细介绍,请参考本文附录章节。

2 使用 jemalloc 工具解决实际业务中遇到 Native method 内存泄漏问题

毕昇 JDK 某个版本内部迭代开发期间,在特性功能开发测试完毕后,进行 7*24 小时长稳测试时发现开启 - XX:+UseG1GC 选项会导致内存迅速增加,怀疑 JVM 层面存在内存泄露问题。

Java 例子参考(例子仅作为帮助问题理解使用):

import java.util.LinkedHashMap;

public class SystemGCTest {
    static int Xmx = 10;
    private static final int MB = 1024 * 1024;
    private static byte[] dummy;
    private static Integer[] funny;
    private static LinkedHashMap<integer, integer[]=""> map = new LinkedHashMap&lt;&gt;();

    public static void main(String[] args) {
        int loop = Integer.valueOf(args[0]);
        if (loop &lt; 0) {
            loop = loop * -1;
            while (true) {
                doGc(loop);
                map.clear();
                System.gc();
            }
        } else {
            doGc(loop);
            map.clear();
            System.gc();
        }
    }

    private static void doGc(int numberOfTimes) {
        final int objectSize = 128;
        final int maxObjectInYoung = (Xmx * MB) / objectSize;
        for (int i = 0; i &lt; numberOfTimes; i++) {
            for (int j = 0; j &lt; maxObjectInYoung + 1; j++) {
                dummy = new byte[objectSize];
                funny = new Integer[objectSize / 16];
                if (j % 10 == 0) {
                    map.put(Integer.valueOf(j), funny);
                }
            }
        }
    }
}

image.png

上图是开启 - XX:+UseG1GC 选项,Java 进程内存增长曲线图。横坐标是内存使用的统计次数,每 10 分钟统计一次;纵坐标是 Java 进程占用物理内存的大小。从上图可以看出:物理内存持续增涨的速度很快,存在内存泄露问题。

我们在设置了 jemalloc 的环境下,重新运行该测试用例:

java -Xms10M -Xmx10M -XX:+UseG1GC SystemGCTest 10000

注意:10000 与 jemalloc 无关,是 SystemGCTest 测试用例的参数,java 是疑似存在内存泄漏的 Java 二进制文件。

程序启动后,会在当前目录下逐渐生成一些 heap 文件,格式如:jeprof.26205.0.i0.heap。jeprof 工具的环境变量设置正确后(可参考本文附录),开发可以直接执行 jeprof 命令查看运行结果,了解 jeprof 的使用方式。jeprof 可基于 jemalloc 生成的内存 profile 堆文件,进行堆文件解析、分析并生成用户容易理解的文件,工具使用方便。

下面我们通过上述内存泄露问题,简单介绍 jeprof 工具的典型使用方法。

jeprof 工具可以生成内存申请代码的调用路径图。上述 Java 例子运行一段时间后会产生一些 heap 文件,jeprof 可帮助开发者获取有助于分析的可视化文件。

方法 1,通过使用 jeprof 工具将这些 heap 文件转成 svg 文件,命令如下:

jeprof --show_bytes --svg /home/xxxx/jdk1.8.0_292/bin/java jeprof*.heap &gt; app-profiling.svg

这里需要注意的是:/home/xxxx/jdk1.8.0_292/bin/java 必须是绝对路径。

注意:执行生成 svg 文件的命令时,部分环境会遇到类似如下错误:

Dropping nodes with &lt;= 2140452 B; edges with &lt;= 428090 abs(MB)
 dot: command not found

该问题的解决方法,需要在环境中安装 graphviz 和 gv:

sudo apt install graphviz gv

安装成功后,再次执行方法 1 中命令,可以得到可视化 svg 文件。

测试用例执行三十分钟后,我们对最后十分钟的内存增长进行分析,结果发现:95.9% 的内存增长来自 G1DefaultParGCAllocator 的构造函数调用,这里的最后 10 分钟是和用例设置相关,如下图所示:

image.png

上图比较清晰显示了内存申请相关函数的调用关系以及内存申请比例和数量,约95.9% 的堆内存是通过 G1DefaultParGCAllocator 完成申请,可以预测在 G1DefaultParGCAllocator 的构造函数中申请的内存没有被及时回收掉而导致内存泄漏的可能性非常大。这个时候可以通过代码协助分析了。

jeprof 工具不仅可以查看详细信息或者生成调用路径图(如上图所示),还可以用来比较两个 dump 文件(显示增量部分),既然作为工具使用介绍,我们继续介绍另一种补充性分析方法:将连续两次的 heap 文件做差异对比,输出的 PDF 可视化文件可以进一步确定是哪里内存持续申请没有被释放而导致内存增长。

方法如下:

jeprof --base=jeprof.34070.0.i0.heap  --pdf /home/xxxx/jdk1.8.0_292/bin/java jeprof.34070.1.i1.heap &gt; diff.pdf

内存增加差异图:

image.png

通过上图可以非常清晰看到:G1DefaultParGCAllocator 的构造函数持续申请内存,导致内存增长迅速。后续的工作就是针对 G1DefaultParGCAllocator 构造函数中内存申请情况,排查释放逻辑,寻找问题原因并解决,这块的工作不属于 jemalloc 范畴,本内容不再赘述。

代码修复后 Java 进程物理内存使用情况如下(运行 30 小时 +):

image.png

内存使用符合预期,问题解决。

通过 jemalloc 工具和上面介绍的方法,帮助开发快速解决了此特性引起 Native method 内存泄漏问题,方法使用简单。在实际业务中有遇到类似问题的同学,不妨亲自尝试一下。

附录 A jemalloc 的编译

jemalloc 普通版并不包含 profiling 机制,所以需要下载源代码重新编译,在 configure 的时候添加了 --enable-prof 选项,这样才能打开 profiling 机制。

A.1 下载最新版本 jemalloc 源码

git clone https://github.com/jemalloc/jemalloc.git

A.2 jemalloc 源码构建

  1. 修改 autogen.sh 文件,使能 prof,如下:
diff --git a/autogen.sh b/autogen.sh
index 75f32da6..6ab4053c 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -9,8 +9,8 @@ for i in autoconf; do
     fi
 done

-echo "./configure --enable-autogen $@"
-./configure --enable-autogen $@
+echo "./configure --enable-prof $@"
+./configure --enable-prof $@
 if [ $? -ne 0 ]; then
     echo "Error $? in ./configure"
     exit 1

执行:

$ ./autogen.sh
$ make -j 6

以下命令可选:

$ make install
  1. 源码构建成功后(一般不会出错),会在当前目录的 bin 和 lib 目录下生成重要的文件:
$ ls -l
total 376
-rw-rw-r-- 1 xxxx xxxx   1954 Jun 19 06:16 jemalloc-config
-rw-rw-r-- 1 xxxx xxxx   1598 Jun 19 06:12 jemalloc-config.in
-rw-rw-r-- 1 xxxx xxxx    145 Jun 19 06:16 jemalloc.sh
-rw-rw-r-- 1 xxxx xxxx    151 Jun 19 06:12 jemalloc.sh.in
-rw-rw-r-- 1 xxxx xxxx 182460 Jun 19 06:16 jeprof
-rw-rw-r-- 1 xxxx xxxx 182665 Jun 19 06:12 jeprof.in
$ cd ../lib/
$ ls -l
total 89376
-rw-rw-r-- 1 xxxx xxxx 42058434 Jun 19 06:19 libjemalloc.a
-rw-rw-r-- 1 xxxx xxxx 42062016 Jun 19 06:19 libjemalloc_pic.a
lrwxrwxrwx 1 xxxx xxxx       16 Jun 19 06:19 libjemalloc.so -&gt; libjemalloc.so.2
-rwxrwxr-x 1 xxxx xxxx  7390832 Jun 19 06:19 libjemalloc.so.2
$ pwd
/home/xxxx/jemalloc/jemalloc/lib
  1. 设置环境变量和执行权限

bin 目录下的 jeprof 文件,没有执行权限,需要设置一下:

bin$ chmod +x ./*

退到 bin 的上一层目录设置环境变量,可参考如下方法:

xxxx@hostname:jemalloc$ echo $JEMALLOC_DIR
 xxxx@hostname:jemalloc$ export JEMALLOC_DIR=`pwd`
 xxxx@hostname:jemalloc$ echo $JEMALLOC_DIR
 /home/xxxx/jemalloc/jemalloc
 xxxx@hostname:jemalloc$ export LD_PRELOAD=$JEMALLOC_DIR/lib/libjemalloc.so
 xxxx@hostname:jemalloc$ export MALLOC_CONF=prof:true,lg_prof_interval:30,lg_prof_sample:17
 xxxx@hostname:jemalloc$ which jeprof
 xxxx@hostname:jemalloc$ export PATH=$PATH:$JEMALLOC_DIR/bin
 xxxx@hostname:jemalloc$ which jeprof
 /home/xxxx/jemalloc/jemalloc/bin/jeprof
 xxxx@hostname:jemalloc$ jeprof --version
 jeprof (part of jemalloc 5.2.1-737-g2381efab5754d13da5104b101b1e695afb442590)
 based on pprof (part of gperftools 2.0)

 Copyright 1998-2007 Google Inc.

 This is BSD licensed software; see the source for copying conditions
 and license information.
 There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
 PARTICULAR PURPOSE.

到这一步,jeprof 可以在该环境中启动使用了。

后记

如果遇到相关技术问题(包括不限于毕昇 JDK),可以进入毕昇 JDK 社区查找相关资源(点击阅读原文进入官网),包括二进制下载、代码仓库、使用教学、安装、学习资料等。毕昇 JDK 社区每双周周二举行技术例会,同时有一个技术交流群讨论 GCC、LLVM、JDK 和 V8 等相关编译技术,感兴趣的同学可以添加如下微信小助手,回复 Compiler 入群。

与[转帖]【技术剖析】11. 使用jemalloc解决JVM内存泄露问题相似的内容:

[转帖]【技术剖析】11. 使用jemalloc解决JVM内存泄露问题

https://bbs.huaweicloud.com/forum/thread-169523-1-1.html 作者:王坤 > 编者按:JVM 发生内存泄漏,如何能快速定位到内存泄漏点并不容易。笔者通过使用 jemalloc(可以替换默认的 glibc 库)中的 profiling 机制(通过对程

[转帖]【技术剖析】18. Native Memory Tracking 详解(4):使用 NMT 协助排查内存问题案例

https://bbs.huaweicloud.com/forum/thread-0211103793043202049-1-1.html 其他 发表于 2022-11-14 15:38:571174查看 从前面几篇文章,我们了解了 NMT 的基础知识以及 NMT 追踪区域分析的相关内容,本篇文章将

[转帖]【技术剖析】5. JDK 从8升级到11,使用 G1 GC,HBase 性能下降近20%。JDK 到底干了什么?

https://bbs.huaweicloud.com/forum/thread-145649-1-1.html 发表于 2021-08-04 10:22:135894查看 作者:林军军、彭成寒 编者按:笔者在 HBase 业务场景中尝试将 JDK 从 8 升级到 11,使用 G1 GC 作为垃圾回

[转帖]【技术剖析】9. 使用 NMT 和 pmap 解决 JVM 资源泄漏问题

https://bbs.huaweicloud.com/forum/thread-168749-1-1.html 作者:宋尧飞 > 编者按:笔者使用 JDK 自带的内存跟踪工具 NMT 和 Linux 自带的 pmap 解决了一个非常典型的资源泄漏问题。这个资源泄漏是由于 Java 程序员不正确地使

[转帖]【技术剖析】16. Native Memory Tracking 详解(2):追踪区域分析(一)

https://bbs.huaweicloud.com/forum/thread-0295101552606827089-1-1.html 上篇文章 Native Memory Tracking 详解(1):基础介绍 中,分享了如何使用NMT,以及NMT内存 & OS内存概念的差异性,本篇将介绍NM

[转帖]【技术剖析】8. 相同版本 JVM 和 Java 应用,在 x86 和AArch64 平台性能相差30%,何故?

https://bbs.huaweicloud.com/forum/thread-168532-1-1.html 作者: 吴言 > 编者按:目前许多公司同时使用 x86 和 AArch64 2 种主流的服务器。这两种环境的算力相当,内存相同的情况下:相同版本的 JVM 和 Java 应用,相同的 J

[转帖]【技术剖析】15. Native Memory Tracking 详解(1):基础介绍

https://bbs.huaweicloud.com/forum/thread-0246998875346680043-1-1.html 0.引言 我们经常会好奇,我启动了一个 JVM,他到底会占据多大的内存?他的内存都消耗在哪里?为什么 JVM 使用的内存比我设置的 -Xmx 大这么多?我的内存

[转帖]【技术剖析】17. Native Memory Tracking 详解(3):追踪区域分析(二)

https://bbs.huaweicloud.com/forum/thread-0227103792775240073-1-1.html 应用性能调优 发表于 2022-11-14 15:19:36143查看 上篇文章 Native Memory Tracking 详解(2):追踪区域分析(一) 

[转帖]【技术剖析】10. JVM 中不正确的类加载顺序导致应用运行异常问题分析

https://bbs.huaweicloud.com/forum/thread-169439-1-1.html 神Bug... 发表于 2021-11-15 10:36:113973查看 作者:程经纬、谢照昆 > 编者按:两位笔者分享了不同的案例,一个是因为 JDK 小版本升级后导致运行出错,最终

[转帖]【技术剖析】12. 毕昇 JDK 8 中 AppCDS 实现介绍

https://bbs.huaweicloud.com/forum/thread-169622-1-1.html 作者:伍家华 > 编者按:笔者通过在 Hive 的场景发现 AppCDS 技术存在的价值,然后分析了 AppCDS 的工作原理,并将 JDK 11 中的特性移植到毕昇 JDK 8,在移植