不断增长的 Java 进程的驻留内存使用量 (RSS)

2022-09-01 20:23:49

我们最近对生产系统的观察告诉我们,Java容器的驻留内存使用量正在增长。关于这个问题,我们已经做了一些调查来理解,为什么java进程比堆+线程栈+共享对象+代码缓存+等消耗更多的内存,使用一些原生工具,如pmap。因此,我们发现一些64M内存块(成对)由本机进程(可能使用malloc / mmap)分配:

0000000000400000      4K r-x--  /usr/java/jdk1.7.0_17/bin/java
0000000000600000      4K rw---  /usr/java/jdk1.7.0_17/bin/java
0000000001d39000   4108K rw---    [ anon ]
0000000710000000  96000K rw---    [ anon ]
0000000715dc0000  39104K -----    [ anon ]
00000007183f0000 127040K rw---    [ anon ]
0000000720000000 3670016K rw---    [ anon ]
00007fe930000000  62876K rw---    [ anon ]
00007fe933d67000   2660K -----    [ anon ]
00007fe934000000  20232K rw---    [ anon ]
00007fe9353c2000  45304K -----    [ anon ]
00007fe938000000  65512K rw---    [ anon ]
00007fe93bffa000     24K -----    [ anon ]
00007fe940000000  65504K rw---    [ anon ]
00007fe943ff8000     32K -----    [ anon ]
00007fe948000000  61852K rw---    [ anon ]
00007fe94bc67000   3684K -----    [ anon ]
00007fe950000000  64428K rw---    [ anon ]
00007fe953eeb000   1108K -----    [ anon ]
00007fe958000000  42748K rw---    [ anon ]
00007fe95a9bf000  22788K -----    [ anon ]
00007fe960000000   8080K rw---    [ anon ]
00007fe9607e4000  57456K -----    [ anon ]
00007fe968000000  65536K rw---    [ anon ]
00007fe970000000  22388K rw---    [ anon ]
00007fe9715dd000  43148K -----    [ anon ]
00007fe978000000  60972K rw---    [ anon ]
00007fe97bb8b000   4564K -----    [ anon ]
00007fe980000000  65528K rw---    [ anon ]
00007fe983ffe000      8K -----    [ anon ]
00007fe988000000  14080K rw---    [ anon ]
00007fe988dc0000  51456K -----    [ anon ]
00007fe98c000000  12076K rw---    [ anon ]
00007fe98cbcb000  53460K -----    [ anon ]

我用0000000720000000 3670016K来解释这条线,指的是堆空间,我们使用JVM参数“-Xmx”定义的大小。在那之后,货币对开始,其中的总和恰好是64M。我们使用的是 CentOS 5.10 版(最终版)64 位架构和 JDK 1.7.0_17 版。

问题是,这些块是什么?哪个子系统分配这些?

更新:我们不使用 JIT 和/或 JNI 本机代码调用。


答案 1

也可能存在本机内存泄漏。一个常见的问题是由于未关闭 / 而导致的本机内存泄漏。ZipInputStreamGZIPInputStream

打开 a 的典型方式是调用 / 并调用实例或调用 /。必须确保这些流始终关闭。ZipInputStreamClass.getResourceClassLoader.getResourceopenConnection().getInputStream()java.net.URLClass.getResourceAsStreamClassLoader.getResourceAsStream

一些常用的开源库存在未关闭或实例泄漏的错误。例如,Nimbus Jose JWT库在6.5.1版本中修复了相关的内存泄漏。Java JWT(jjwt)有一个类似的错误,在0.10.7版本中得到了修复。这两种情况下的错误模式是,当提供 / 实例时,调用和不调用 / 。在这些情况下,仅检查代码中是否有正在关闭的流是不够的。在代码中创建的每个 / 实例都必须具有被调用的处理。java.util.zip.Inflaterjava.util.zip.DeflaterDeflaterOutputStream.close()InflaterInputStream.close()Deflater.end()Inflater.end()DeflaterInflaterDeflaterInflater.end()

检查 Zip*Stream 泄漏的一种方法是获取堆转储并搜索名称中包含“zip”、“Inflater”或“Deflater”的任何类的实例。这在许多堆转储分析工具中都是可能的,例如Yourkit Java Profiler,JProfiler或Eclipse MAT。检查处于终结状态的对象也是值得的,因为在某些情况下,内存只有在终结后才会释放。检查可能使用本机库的类非常有用。这也适用于 TLS/ssl 库。

Elastic有一个名为泄漏检查器的OSS工具,它是一个Java代理,可用于查找尚未关闭(未调用)的实例的源。java.util.zip.Inflater.end()

对于一般的本机内存泄漏(不仅仅是 zip 库泄漏),可以使用 jemalloc 通过通过在环境变量中指定设置来启用 malloc 采样分析来调试本机内存泄漏。此博客文章中提供了详细说明: http://www.evanjones.ca/java-native-leak-bug.html这篇博客文章还提供了有关使用jemalloc调试java应用程序中的本机内存泄漏的信息。Elastic还有一篇博客文章,其中提到了jemalloc并提到了泄漏检查器,这是Elastic开源的工具,用于跟踪由未关闭的zip inflater资源引起的问题。MALLOC_CONF

还有一篇关于与ByteBuffers相关的本机内存泄漏的博客文章Java 8u102 具有一个特殊的系统属性,用于限制该博客文章中描述的高速缓存问题。jdk.nio.maxCachedBufferSize

-Djdk.nio.maxCachedBufferSize=262144

最好始终检查打开的文件句柄,以查看内存泄漏是否由大量 mmap:ed 文件引起。在 Linux 上可用于列出打开的文件和打开的套接字:lsof

lsof -Pan -p PID

进程的内存映射报告还可以帮助调查本机内存泄漏

pmap -x PID

对于在 Docker 中运行的 Java 进程,应该可以在“主机”上执行 lsof 或 pmap 命令。您可以使用此命令找到容器化进程的 PID

docker inspect --format '{{.State.Pid}}' container_id

获取线程转储(或使用 jconsole/JMX)来检查线程数也很有用,因为每个线程的堆栈消耗 1MB 的本机内存。大量线程将使用大量内存。

JVM中还有本机内存跟踪(NMT)。这可能有助于检查是否是 JVM 本身正在耗尽本机内存。

jattach 工具也可以在容器化(docker)环境中使用,以从主机触发线程转储或堆转储。它还能够运行控制NMT所需的jcmd命令。


答案 2

我遇到了同样的问题。这是 glibc 的已知问题,>= 2.10

解决方法是设置此 env 变量 export MALLOC_ARENA_MAX=4

有关设置MALLOC_ARENA_MAX https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=en IBM 文章

谷歌MALLOC_ARENA_MAX或在SO上搜索它以找到很多参考资料。

您可能还希望调整其他 malloc 选项,以针对已分配内存的低碎片进行优化:

# tune glibc memory allocation, optimize for low fragmentation
# limit the number of arenas
export MALLOC_ARENA_MAX=2
# disable dynamic mmap threshold, see M_MMAP_THRESHOLD in "man mallopt"
export MALLOC_MMAP_THRESHOLD_=131072
export MALLOC_TRIM_THRESHOLD_=131072
export MALLOC_TOP_PAD_=131072
export MALLOC_MMAP_MAX_=65536