如何在 JVM 上调试本机内存中的泄漏?

Posted

技术标签:

【中文标题】如何在 JVM 上调试本机内存中的泄漏?【英文标题】:How to debug leak in native memory on JVM? 【发布时间】:2016-11-04 08:04:11 【问题描述】:

我们有一个在 Mule 上运行的 java 应用程序。我们将 XMX 值配置为 6144M,但经常看到整体内存使用量不断攀升。前几天我们主动重新启动它之前,它已经接近 20 GB。

Thu Jun 30 03:05:57 CDT 2016
top - 03:05:58 up 149 days,  6:19,  0 users,  load average: 0.04, 0.04, 0.00
Tasks: 164 total,   1 running, 163 sleeping,   0 stopped,   0 zombie
Cpu(s):  4.2%us,  1.7%sy,  0.0%ni, 93.9%id,  0.2%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:  24600552k total, 21654876k used,  2945676k free,   440828k buffers
Swap:  2097144k total,    84256k used,  2012888k free,  1047316k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 3840 myuser  20   0 23.9g  18g  53m S  0.0 79.9 375:30.02 java

jps命令显示:

10671 Jps
3840 MuleContainerBootstrap

jstat 命令显示:

 S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT
37376.0 36864.0 16160.0  0.0   2022912.0 1941418.4 4194304.0   445432.2  78336.0 66776.7    232    7.044  17     17.403   24.447

启动参数是(敏感位已更改):

3840 MuleContainerBootstrap -Dmule.home=/mule -Dmule.base=/mule -Djava.net.preferIPv4Stack=TRUE -XX:MaxPermSize=256m -Djava.endorsed.dirs=/mule/lib/endorsed -XX:+HeapDumpOnOutOfMemoryError -Dmyapp.lib.path=/datalake/app/ext_lib/ -DTARGET_ENV=prod -Djava.library.path=/opt/mapr/lib -DksPass=mypass -DsecretKey=aeskey -DencryptMode=AES -Dkeystore=/mule/myStore -DkeystoreInstance=JCEKS -Djava.security.auth.login.config=/opt/mapr/conf/mapr.login.conf -Dmule.mmc.bind.port=1521 -Xms6144m -Xmx6144m -Djava.library.path=%LD_LIBRARY_PATH%:/mule/lib/boot -Dwrapper.key=a_guid -Dwrapper.port=32000 -Dwrapper.jvm.port.min=31000 -Dwrapper.jvm.port.max=31999 -Dwrapper.disable_console_input=TRUE -Dwrapper.pid=10744 -Dwrapper.version=3.5.19-st -Dwrapper.native_library=wrapper -Dwrapper.arch=x86 -Dwrapper.service=TRUE -Dwrapper.cpu.timeout=10 -Dwrapper.jvmid=1 -Dwrapper.lang.domain=wrapper -Dwrapper.lang.folder=../lang

将 jps 的“容量”项加起来表明只有我的 6144m 用于 java 堆。其余的内存在哪里使用?堆栈内存?本机堆?我什至不知道如何继续。

如果继续增长,它将消耗系统上的所有内存,我们最终会看到系统冻结并抛出交换空间错误。

我有另一个进程正在开始增长。目前大约 11g 常驻内存。

pmap 10746 > pmap_10746.txt
cat pmap_10746.txt | grep anon | cut -c18-25 | sort -h | uniq -c | sort -rn | less

Top 10 entries by count:
    119     12K
    112   1016K
     56      4K
     38 131072K
     20  65532K
     15 131068K
     14  65536K
     10    132K
      8  65404K
      7    128K


Top 10 entries by allocation size:
     1 6291456K
      1 205816K
      1 155648K
     38 131072K
     15 131068K
      1 108772K
      1  71680K
     14  65536K
     20  65532K
      1  65512K

And top 10 by total size:
Count   Size    Aggregate
1   6291456K    6291456K
38  131072K 4980736K
15  131068K 1966020K
20  65532K  1310640K
14  65536K  917504K
8   65404K  523232K
1   205816K 205816K
1   155648K 155648K
112 1016K   113792K

这似乎在告诉我,因为 Xmx 和 Xms 设置为相同的值,所以 Java 堆的单个分配为 6291456K。其他分配不是 java 堆内存。这些是什么?它们被分配到相当大的块中。

【问题讨论】:

不是一个正确的答案,但我在网络缓冲区中遇到过这种情况。显然,如果您订阅多播并且不阅读它(或缓慢阅读),入站缓冲区可能会增长到巨大的大小(我观察到 10 GB)。也许其他网络也是如此。我还没有找到一种有效分析进程内存转储的方法,不幸的是,我什至没有粗略的看法(并且会对实际答案非常感兴趣)。 检查直接 ByteBuffer 内存(可通过 MXBean java.nio:type=BufferPool 获得)。也可以试试Native Memory Tracking 功能。 我仍在运行 Java 7,所以看起来本机内存跟踪对我来说不是一个选项。不过看起来真的很有用。 【参考方案1】:

详细介绍彼得的答案。

您可以从 VisualVM 中获取二进制堆转储(右键单击左侧列表中的进程,然后单击堆转储 - 不久之后它会出现在下方)。如果您无法将 VisualVM 附加到您的 JVM,您也可以使用以下命令生成转储:

jmap -dump:format=b,file=heap.hprof $PID

然后复制文件并用Visual VM打开(文件,加载,选择类型堆转储,找到文件。)

正如 Peter 所指出的,泄漏的一个可能原因可能是未收集的 DirectByteBuffers(例如:另一个类的某些实例未正确取消引用缓冲区,因此它们永远不会被 GC 处理)。

要确定这些引用来自何处,您可以使用 Visual VM 检查堆并在“类”选项卡中找到 DirectByteByffer 的所有实例。找到 DBB 类,右键单击,转到实例视图。

这将为您提供实例列表。您可以单击一个,看看谁在保留每个参考:

注意底部窗格,我们有类型为 Cleaner 的“referent”和 2 个“mybuffer”。这些将是其他类中引用我们钻取的 DirectByteBuffer 实例的属性(如果您忽略 Cleaner 并专注于其他类应该没问题)。

从此时起,您需要根据您的申请继续。

获取 DBB 实例列表的另一种等效方法是从 OQL 选项卡。这个查询:

select x from java.nio.DirectByteBuffer x

为我们提供与以前相同的列表。使用 OQL 的好处是可以执行更多的more complex queries。例如,这会获取所有保持对 DirectByteBuffer 的引用的实例:

select referrers(x) from java.nio.DirectByteBuffer x

【讨论】:

感谢您的详尽解释。 VisualVM 无​​法加载我的堆转储,但 MAT 可以。即使在强制 gc 之后,它也显示了 130 万个 DirectByteBuffer 对象。其中大部分与 com.mapr.fs.jni.Page 相关联。我已经联系了他们的支持。【参考方案2】:

您可以做的是进行堆转储并查找在堆外存储数据的对象,例如 ByteBuffers。这些对象看起来很小,但代表较大的堆外内存区域。看看你是否能确定为什么会保留很多这些。

【讨论】:

以上是关于如何在 JVM 上调试本机内存中的泄漏?的主要内容,如果未能解决你的问题,请参考以下文章

如何避免本机反应中的内存泄漏?

如何从android中的本机代码中查找内存泄漏

如何从托管 C# 代码跟踪 CRT 调试内存泄漏输出的来源?

如何使用 Malloc Debug 来检查本机内存泄漏?

如何在 Jboss AS 5.1 中追踪非堆 JVM 内存泄漏?

vc 怎么 生成 dump 文件