Docker 容器中运行的 JVM 的驻留集大小 (RSS) 和 Java 总提交内存 (NMT) 之间的差异

2024-04-05

设想:

我有一个 JVM 在 Docker 容器中运行。我使用两种工具进行了一些内存分析:1)top 2) Java 本机内存跟踪。这些数字看起来令人困惑,我正在尝试找出造成差异的原因。

问题:

Java 进程的 RSS 报告为 1272MB,总 Java 内存报告为 790.55 MB。我如何解释剩余的内存 1272 - 790.55 = 481.44 MB 去了哪里?

为什么我在查看之后仍想保持这个问题的开放性这个问题 https://stackoverflow.com/questions/31173374 on SO:

我确实看到了答案并且解释很有道理。然而,从 Java NMT 和 pmap -x 获得输出后,我仍然无法具体映射哪些java内存地址实际上驻留和物理映射。我需要一些具体的解释(带有详细的步骤)来找出导致 RSS 和 Java 总提交内存之间差异的原因。

顶部输出

Java NMT

Docker 内存统计

Graphs

我有一个运行了超过 48 小时的 docker 容器。现在,当我看到一个图表包含:

  1. 分配给 docker 容器的总内存 = 2 GB
  2. Java 最大堆 = 1 GB
  3. 总提交量 (JVM) = 始终小于 800 MB
  4. 已用堆 (JVM) = 始终小于 200 MB
  5. 未使用堆 (JVM) = 始终小于 100 MB。
  6. RSS = 约 1.1 GB。

那么,是什么占用了 1.1 GB (RSS) 和 800 MB(Java 总提交内存)之间的内存呢?


你有一些线索“分析 Docker 容器中的 java 内存使用情况 http://trustmeiamadeveloper.com/2016/03/18/where-is-my-memory-java/" from 米哈伊尔·克莱斯特加尼诺夫 https://github.com/krestjaninoff:

(需要明确的是,三年后的 2019 年 5 月,the 情况确实有所改善使用 openJDK 8u212 https://stackoverflow.com/a/56153528/6309 )

Resident Set Size 是进程当前分配和使用的物理内存量(没有换出页面)。它包括代码、数据和共享库(在使用它们的每个进程中都会计算在内)

为什么 docker stats 信息与 ps 数据不同?

第一个问题的答案很简单——Docker 有一个错误(或一个功能 - 取决于你的心情) https://github.com/docker/docker/issues/10824:它将文件缓存包含在总内存使用信息中。因此,我们可以避免这个指标并使用ps有关 RSS 的信息。

好吧,好吧——但是为什么RSS比Xmx高?

理论上,如果是 java 应用程序

RSS = Heap size + MetaSpace + OffHeap size

其中 OffHeap 由线程堆栈、直接缓冲区、映射文件(库和 jar)和 JVM 代码组成

Since JDK 1.8.40 https://docs.oracle.com/javase/8/docs/technotes/guides/vm/enhancements-8.html我们有本机内存跟踪器 https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr007.html!

如你所见,我已经添加了-XX:NativeMemoryTracking=summary属性传递给 JVM,因此我们可以从命令行调用它:

docker exec my-app jcmd 1 VM.native_memory summary

(这就是OP所做的)

不要担心“未知”部分 - 似乎 NMT 是一个不成熟的工具,无法处理 CMS GC(当您使用另一个 GC 时,此部分消失)。

记住,NMT 显示“已提交”内存,而不是“驻留”内存(通过 ps 命令获得)。换句话说,内存页可以在不考虑驻留的情况下提交(直到它被直接访问).

这意味着非堆区域(堆始终预初始化)的 NMT 结果可能大于 RSS 值.

(这就是“为什么 JVM 报告的已提交内存多于 Linux 进程驻留集大小? https://stackoverflow.com/q/31173374/6309“ 进来)

结果,尽管我们将 jvm 堆限制设置为 256M,我们的应用程序还是消耗了 367M。 “其他”164M 主要用于存储类元数据、编译代码、线程和 GC 数据。

前三点对于应用程序来说通常是常量,因此唯一随堆大小增加的是 GC 数据。
这种依赖性是线性的,但是“k“ 系数 (y = kx + b) 远小于 1。


更一般地说,这似乎遵循问题 15020 https://github.com/docker/docker/issues/15020自 docker 1.7 以来报告了类似的问题

我正在运行一个简单的 Scala (JVM) 应用程序,它将大量数据加载到内存中或从内存中加载出来。
我将 JVM 设置为 8G 堆(-Xmx8G)。我有一台内存为 132G 的机器,它无法处理超过 7-8 个容器,因为它们的增长远远超出了我对 JVM 施加的 8G 限制。

(docker stat https://docs.docker.com/engine/reference/commandline/stats/ was 之前被报道为误导性的 https://github.com/docker/docker/issues/10824#issuecomment-130681068,因为它显然将文件缓存包含在总内存使用信息中)

docker stat https://docs.docker.com/engine/reference/commandline/stats/显示每个容器本身使用的内存比 JVM 应该使用的内存多得多。例如:

CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O
dave-1 3.55% 10.61 GB/135.3 GB 7.85% 7.132 MB/959.9 MB
perf-1 3.63% 16.51 GB/135.3 GB 12.21% 30.71 MB/5.115 GB

看起来 JVM 正在向操作系统请求内存,该内存是在容器内分配的,并且 JVM 在 GC 运行时释放内存,但容器不会将内存释放回主操作系统。所以...内存泄漏。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Docker 容器中运行的 JVM 的驻留集大小 (RSS) 和 Java 总提交内存 (NMT) 之间的差异 的相关文章

随机推荐