【问题标题】:Growing resident memory usage (RSS) of Java ProcessJava 进程的常驻内存使用量 (RSS) 不断增长
【发布时间】:2014-11-20 09:01:45
【问题描述】:

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

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 release 5.10 (Final) 64-bit arch 和 JDK 1.7.0_17 。

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

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

【问题讨论】:

  • 您是否使用 VisualVM 或类似工具分析过您的 java 应用程序?
  • 使用VisualVM,只能在堆空间内进行监控。

标签: java linux unix memory


【解决方案1】:

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

的一个已知问题

解决方法是设置这个环境变量 export MALLOC_ARENA_MAX=4

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

谷歌 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

【讨论】:

【解决方案2】:

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

打开ZipInputStream 的典型方式是调用Class.getResource/ClassLoader.getResource 并在java.net.URL 实例上调用openConnection().getInputStream(),或调用Class.getResourceAsStream/ClassLoader.getResourceAsStream。必须确保这些流始终关闭。

一些常用的开源库存在泄漏未关闭的java.util.zip.Inflaterjava.util.zip.Deflater 实例的错误。例如,Nimbus Jose JWT 库在 6.5.1 版本中有fixed a related memory leak。 Java JWT (jjwt) 在 0.10.7 版本中有 a similar bug that was fixed。这两种情况下的错误模式是,当提供Deflater/Inflater 实例时,对DeflaterOutputStream.close()InflaterInputStream.close() 的调用不会调用Deflater.end()/Inflater.end()。在这些情况下,仅检查代码是否已关闭流是不够的。在代码中创建的每个 Deflater/Inflater 实例都必须处理 .end() 被调用。

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

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

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

还有一篇关于native memory leak related to 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 本机内存。大量线程会占用大量内存。

还有Native Memory Tracking (NMT) in the JVM。这对于检查是否是 JVM 本身正在耗尽本机内存可能很有用。

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

【讨论】:

猜你喜欢
  • 2019-12-10
  • 2023-03-09
  • 2017-04-02
  • 2019-11-12
  • 2015-02-10
  • 1970-01-01
  • 2017-11-09
  • 2013-10-08
相关资源
最近更新 更多