为什么 JVM 报告的提交内存多于 linux 进程驻留集大小?

Why does a JVM report more committed memory than the linux process resident set size?

当 运行 一个 Java 应用程序(在 YARN 中)启用了本机内存跟踪时(-XX:NativeMemoryTracking=detail 参见 https://docs.oracle.com/javase/8/docs/technotes/guides/vm/nmt-8.html and https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr007.html),我可以看到 JVM 有多少内存在不同类别中使用。

我在 jdk 1.8.0_45 上的应用程序显示:

Native Memory Tracking:

Total: reserved=4023326KB, committed=2762382KB
-                 Java Heap (reserved=1331200KB, committed=1331200KB)
                            (mmap: reserved=1331200KB, committed=1331200KB) 

-                     Class (reserved=1108143KB, committed=64559KB)
                            (classes #8621)
                            (malloc=6319KB #17371) 
                            (mmap: reserved=1101824KB, committed=58240KB) 

-                    Thread (reserved=1190668KB, committed=1190668KB)
                            (thread #1154)
                            (stack: reserved=1185284KB, committed=1185284KB)
                            (malloc=3809KB #5771) 
                            (arena=1575KB #2306)

-                      Code (reserved=255744KB, committed=38384KB)
                            (malloc=6144KB #8858) 
                            (mmap: reserved=249600KB, committed=32240KB) 

-                        GC (reserved=54995KB, committed=54995KB)
                            (malloc=5775KB #217) 
                            (mmap: reserved=49220KB, committed=49220KB) 

-                  Compiler (reserved=267KB, committed=267KB)
                            (malloc=137KB #333) 
                            (arena=131KB #3)

-                  Internal (reserved=65106KB, committed=65106KB)
                            (malloc=65074KB #29652) 
                            (mmap: reserved=32KB, committed=32KB) 

-                    Symbol (reserved=13622KB, committed=13622KB)
                            (malloc=12016KB #128199) 
                            (arena=1606KB #1)

-    Native Memory Tracking (reserved=3361KB, committed=3361KB)
                            (malloc=287KB #3994) 
                            (tracking overhead=3075KB)

-               Arena Chunk (reserved=220KB, committed=220KB)
                            (malloc=220KB) 

这显示了 2.7GB 的已提交内存,包括 1.3GB 的分配堆和几乎 1.2GB 的分配线程堆栈(使用许多线程)。

然而,当运行 ps ax -o pid,rss | grep <mypid>top 时,它只显示1.6GB 的RES/rss 常驻内存。检查交换显示 none 正在使用:

free -m
             total       used       free     shared    buffers     cached
Mem:        129180      99348      29831          0       2689      73024
-/+ buffers/cache:      23633     105546
Swap:        15624          0      15624

为什么 JVM 指示 2.7GB 内存已提交,而只有 1.6GB 是驻留的?剩下的去了哪里?

I'm beginning to suspect that stack memory (unlike the JVM heap) seems to be precommitted without becoming resident and over time becomes resident only up to the high water mark of actual stack usage.

是的,至少在 linux 上 mmap 是惰性的,除非另有说明。匿名页面只有在写入后才由物理内存支持(由于zero-page optimization

,读取是不够的

GC 堆内存有效地被复制收集器或预置零 (-XX:+AlwaysPreTouch) 触及,因此它将始终驻留。线程堆栈 otoh 不受此影响。

为了进一步确认,您可以使用 pmap -x <java pid> 并将各种地址范围的 RSS 与 NMT 的虚拟内存映射输出进行交叉引用。


已使用 PROT_NONE 映射保留内存。这意味着虚拟地址 space 范围在内核的 vma 结构中有条目,因此不会被其他 mmap/malloc 调用使用。但是它们仍然会导致页面错误作为 SIGSEGV 转发给进程,即访问它们是错误的。

这对于有可供将来使用的连续地址范围很重要,这反过来又简化了指针运算。

已提交但未支持的存储内存已映射为 - 例如 - PROT_READ | PROT_WRITE 但访问它仍然会导致页面错误。但是内核通过使用实际内存支持它并返回执行来静静地处理该页面错误,就好像什么也没发生一样。
即这是一个不会被进程本身注意到的实现 detail/optimization。


对概念进行细分:

Used Heap: 根据上次GC

存活对象占用的内存量

Committed:已映射到 PROT_NONE 以外的地址范围。由于惰性分配和分页,它们可能有也可能没有物理或交换支持。

保留:通过mmap为特定内存池预映射的总地址范围。
reserved − committed 差异由 PROT_NONE 映射组成,保证不会由物理内存支持

Resident:当前在物理内存中的页面。这意味着代码、堆栈、部分已提交的内存池以及最近访问过的映射文件部分以及 JVM 控制之外的分配。

Virtual:所有虚拟地址映射的总和。涵盖提交的、保留的内存池,还包括映射文件或共享内存。这个数字很少提供信息,因为 JVM 可以提前保留非常大的地址范围或 mmap 大文件。