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



在启用本地内存跟踪的情况下运行Java应用程序(在YARN中)时(-XX:NativeMemoryTracking=detail请参阅https://docs.oracle.com/javase/8/docs/technotes/guides/vm/nmt-8.html和https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr007.html),我可以看到JVM在不同类别中使用了多少内存。

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

本机内存跟踪:总计:保留=4023326KB,提交=2762382KB-Java堆(保留=1331200KB,已提交=1331200KB)(mmap:保留=1331200KB,已提交=1331200KB.)-类(保留=1108143KB,已提交=64559KB)(8621级)(malloc=6319KB#17371)(mmap:保留=1101824KB,已提交=58240KB)-线程(保留=1190668KB,已提交=1190668 KB)(螺纹#1154)(堆栈:保留=1185284KB,已提交=1185284KB)(malloc=3809KB#5771)(竞技场=1575KB#2306)-代码(保留=255744KB,提交=38384KB)(malloc=6144KB#8588)(mmap:保留=249600KB,已提交=32240KB)-GC(保留=54995KB,已提交=54995KB)(malloc=5775KB#217)(mmap:保留=4920KB,已提交=4920KB)-编译器(保留=267KB,已提交=267KB)(malloc=137KB#333)(竞技场=131KB#3)-内部(保留=65106KB,已提交=65106KB)(malloc=65074KB#29652)(mmap:保留=32KB,已提交=32KB)-符号(保留=13622KB,提交=13622KB)(malloc=12016KB#128199)(竞技场=1606KB#1)-本机内存跟踪(保留=3361KB,提交=3361KB)(malloc=287KB#3994)(跟踪开销=3075KB)-Arena Chunk(保留=220KB,已提交=220KB)(malloc=220KB)

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

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

自由-m缓存的已用可用共享缓冲区总数备忘录:129180 99348 29831 0 2689 73024-/+缓冲区/缓存:23633 105546掉期:15624 0 15624

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

我开始怀疑堆栈内存(与JVM堆不同)似乎是在没有驻留的情况下预调试的,并且随着时间的推移,它只驻留到实际堆栈使用的最高水位线。

是的,至少在linux上,mmap是懒惰的,除非另有说明。匿名页面在写入后仅由物理内存支持(由于零页面优化,读取量不足)

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

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


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

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

已提交但未由存储内存备份的内存已映射为(例如)PROT_READ | PROT_WRITE,但访问它仍然会导致页面错误。但是,该页面错误由内核通过用实际内存支持它并像什么都没发生一样返回执行来静默地处理。
也就是说,这是一个过程本身不会注意到的实现细节/优化。


给出概念的分解:

已用堆:根据上一次GC ,活动对象占用的内存量

已提交:已使用PROT_NONE以外的其他内容映射的地址范围。由于延迟分配和分页,它们可能会也可能不会由物理或交换进行备份。

保留:通过mmap为特定内存池预先映射的总地址范围
保留-提交差异由PROT_NONE映射组成,这些映射保证不会由物理内存支持

常驻:当前位于物理ram中的页面。这意味着代码、堆栈、提交的内存池的一部分,以及最近被访问的mmaped文件的一部分和JVM控制之外的分配。

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