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

场景:

我有一个运行在Docker容器中的JVM。 我做了一些内存分析,使用两个工具:1) 顶部 2) Java本机内存跟踪 。 数字看起来令人困惑,我试图找出造成差异的是什么。

题:

Java进程报告RSS为1272MB,总Java内存报告为790.55 MB。 我怎样才能解释其余的内存1272 – 790.55 = 481.44 MB去哪里?

为什么我要在SO上看到这个问题之后继续保持这个问题:

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

顶部输出

在这里输入图像说明

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 Total Commit内存)之间吃什么呢?

在这里输入图像说明

您在Mikhail Krestjaninoff的 “ 分析Docker容器中的Java内存使用情况 ”中有一些线索:

R esident S et Size是进程分配和使用的物理内存量(未换出页面)。 它包括代码,数据和共享库(在使用它们的每个进程中计算)

为什么docker统计信息与ps数据有所不同?

对第一个问题的回答非常简单 – Docker有一个错误(或者一个功能 – 取决于你的心情) :它包括文件缓存到总的内存使用信息中。 所以,我们可以避免这个指标,并使用关于RSS的ps信息。

那好吧 – 但为什么RSS比Xmx更高?

从理论上讲,在Java应用程序的情况下

 RSS = Heap size + MetaSpace + OffHeap size 

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

由于JDK 1.8.40,我们有本机内存跟踪器

正如你所看到的,我已经为JVM添加了-XX:NativeMemoryTracking=summary属性,所以我们可以从命令行调用它:

 docker exec my-app jcmd 1 VM.native_memory summary 

(这是OP做的)

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

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

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

(这就是“ 为什么JVM报告的内存比linux进程驻留集大小多 ”),

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

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


更一般地说,这似乎紧随着问题15020 ,自Docker 1.7以来报告了类似的问题

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

docker stat 之前被docker stat 为误导 ,因为它显然包含了文件缓存到总的内存使用信息中)

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运行时释放内存,但容器不会将内存释放回主操作系统。 所以…内存泄漏。