【问题标题】:Difference between Resident Set Size (RSS) and Java total committed memory (NMT) for a JVM running in Docker container在 Docker 容器中运行的 JVM 的驻留集大小 (RSS) 和 Java 总提交内存 (NMT) 之间的差异
【发布时间】:2016-11-30 13:53:49
【问题描述】:

场景:

我有一个在 docker 容器中运行的 JVM。我使用两个工具做了一些内存分析:1) top 2) Java Native Memory Tracking。这些数字看起来令人困惑,我试图找出导致差异的原因。

问题:

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

为什么即使在查看了 SO 上的 this question 之后,我仍想保持这个问题:

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

热门输出

Java NMT

Docker 内存统计数据

图表

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

  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 提交的总内存)之间的内存是什么?

【问题讨论】:

标签: java linux memory docker jvm


【解决方案1】:

你在Mikhail Krestjaninoff的“ Analyzing java memory usage in a Docker container”中有一些线索:

(需要明确的是,三年后的 2019 年 5 月,the situation does improves with openJDK 8u212

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

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

第一个问题的答案非常简单 - Docker has a bug (or a feature - depends on your mood):它包括文件缓存到总内存使用信息中。所以,我们可以避开这个指标,使用ps RSS 信息。

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

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

RSS = Heap size + MetaSpace + OffHeap size

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

自从JDK 1.8.40我们有了Native Memory Tracker

如您所见,我已经在 J​​VM 中添加了-XX:NativeMemoryTracking=summary 属性,所以我们可以从命令行调用它:

docker exec my-app jcmd 1 VM.native_memory summary

(这就是OP所做的)

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

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

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

(这就是“Why does a JVM report more committed memory than the linux process resident set size?”的来源)

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

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


更一般地说,这似乎是 issue 15020 之后报告了自 docker 1.7 以来的类似问题

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

docker statreported as misleading before,因为它显然将文件缓存包含在总内存使用信息中)

docker stat 表明每个容器本身使用的内存比 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 运行时正在释放内存,但容器不会将内存释放回主操作系统。所以...内存泄漏。

【讨论】:

  • 感谢您的回复!我只是在原始问题上附上了一张图表。问题是,根据您提供的摘要,如果 JVM 向操作系统请求内存,那么当使用的堆增长时,RSS 也应该会猛增。而且,当 GC 发生时,使用的堆会下降,但 RSS 不会。但是,当我看到这些图表时,我确实从 go 一词中看到,RSS 和 Java 总提交内存之间存在相当大的差距。我该如何解释这个差距?
  • @sunsin1985 “1.1 GB (RSS) 和 800 MB (Java 总提交内存) 之间的内存是什么?”我怀疑那些是文章中提到的“存储类元数据、编译代码、线程和 GC 数据”,而且如果容器一直不释放它,GC 可能不会释放任何东西。
  • 谢谢,但这不是从 Java NMT 获得的“总承诺”数字的一部分吗?
  • @sunsin1985 是的(除非您的图表将值堆叠在另一个之上)
  • @sunsin1985 另一个好读物:devcenter.heroku.com/articles/java-memory-issues,其中完整的 java 选项变为 JAVA_OPTS="-XX:NativeMemoryTracking=detail -XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics"
猜你喜欢
  • 2015-09-19
  • 2020-12-21
  • 1970-01-01
  • 2011-03-03
  • 2018-11-24
  • 1970-01-01
  • 2011-12-01
  • 2012-06-06
  • 1970-01-01
相关资源
最近更新 更多