如何调试 JVM 上本机内存中的泄漏?
How to debug leak in native memory on JVM?
我们在 Mule 上有一个 java 应用程序 运行。我们为 6144M 配置了 XMX 值,但通常会看到整体内存使用率不断攀升。前几天我们主动重启它之前它已经接近 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 中的 "capacity" 项目加起来表明只有我的 6144m 被用于 java 堆。剩下的内存到底用在哪里了?栈内存?本机堆?我什至不确定如何进行。
如果继续增长,它将消耗系统上的所有内存,我们最终会看到系统冻结并抛出交换 space 错误。
我有另一个进程开始增长。目前常驻内存在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 堆内存。这些是什么?他们正在以相当大的块分配。
您可以做的是获取堆转储并查找在堆外存储数据的对象,例如 ByteBuffers。这些对象看起来很小,但代表了更大的堆外内存区域。看看您是否可以确定为什么其中很多可能会被保留。
在 Peter 的回答中扩展更多细节。
您可以从 VisualVM 中获取二进制堆转储(右键单击左侧列表中的进程,然后单击堆转储 - 不久之后它会出现在正下方)。如果您无法将 VisualVM 附加到您的 JVM,您也可以使用以下命令生成转储:
jmap -dump:format=b,file=heap.hprof $PID
然后复制文件并使用 Visual VM 打开它(文件,加载,select 键入堆转储,找到文件。)
正如 Peter 所指出的,泄漏的一个可能原因可能是未收集的 DirectByteBuffers(例如:另一个 class 的某些实例未正确取消引用缓冲区,因此它们永远不会被 GC 处理)。
要确定这些引用的来源,您可以使用 Visual VM 检查堆并在 "Classes" 选项卡中找到 DirectByteByffer 的所有实例。找到 DBB class,右键单击,转到实例视图。
这将为您提供一个实例列表。您可以点击一个,看看谁在保留每个参考:
请注意底部窗格,我们有 "referent" 类型的 Cleaner 和 2 "mybuffer"。这些将是其他 classes 中的属性,这些属性引用了我们深入研究的 DirectByteBuffer 实例(如果您忽略 Cleaner 并专注于其他的应该没问题)。
从现在开始,您需要根据您的申请进行处理。
获取 DBB 实例列表的另一种等效方法是从 OQL 选项卡。此查询:
select x from java.nio.DirectByteBuffer x
为我们提供了与之前相同的列表。使用OQL的好处是可以执行更多more complex queries。例如,这会获取所有保持对 DirectByteBuffer 的引用的实例:
select referrers(x) from java.nio.DirectByteBuffer x
我们在 Mule 上有一个 java 应用程序 运行。我们为 6144M 配置了 XMX 值,但通常会看到整体内存使用率不断攀升。前几天我们主动重启它之前它已经接近 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 中的 "capacity" 项目加起来表明只有我的 6144m 被用于 java 堆。剩下的内存到底用在哪里了?栈内存?本机堆?我什至不确定如何进行。
如果继续增长,它将消耗系统上的所有内存,我们最终会看到系统冻结并抛出交换 space 错误。
我有另一个进程开始增长。目前常驻内存在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 堆内存。这些是什么?他们正在以相当大的块分配。
您可以做的是获取堆转储并查找在堆外存储数据的对象,例如 ByteBuffers。这些对象看起来很小,但代表了更大的堆外内存区域。看看您是否可以确定为什么其中很多可能会被保留。
在 Peter 的回答中扩展更多细节。
您可以从 VisualVM 中获取二进制堆转储(右键单击左侧列表中的进程,然后单击堆转储 - 不久之后它会出现在正下方)。如果您无法将 VisualVM 附加到您的 JVM,您也可以使用以下命令生成转储:
jmap -dump:format=b,file=heap.hprof $PID
然后复制文件并使用 Visual VM 打开它(文件,加载,select 键入堆转储,找到文件。)
正如 Peter 所指出的,泄漏的一个可能原因可能是未收集的 DirectByteBuffers(例如:另一个 class 的某些实例未正确取消引用缓冲区,因此它们永远不会被 GC 处理)。
要确定这些引用的来源,您可以使用 Visual VM 检查堆并在 "Classes" 选项卡中找到 DirectByteByffer 的所有实例。找到 DBB class,右键单击,转到实例视图。
这将为您提供一个实例列表。您可以点击一个,看看谁在保留每个参考:
请注意底部窗格,我们有 "referent" 类型的 Cleaner 和 2 "mybuffer"。这些将是其他 classes 中的属性,这些属性引用了我们深入研究的 DirectByteBuffer 实例(如果您忽略 Cleaner 并专注于其他的应该没问题)。
从现在开始,您需要根据您的申请进行处理。
获取 DBB 实例列表的另一种等效方法是从 OQL 选项卡。此查询:
select x from java.nio.DirectByteBuffer x
为我们提供了与之前相同的列表。使用OQL的好处是可以执行更多more complex queries。例如,这会获取所有保持对 DirectByteBuffer 的引用的实例:
select referrers(x) from java.nio.DirectByteBuffer x