【问题标题】:Why does a JVM report more committed memory than the linux process resident set size?为什么 JVM 报告的已提交内存比 linux 进程驻留集大小更多?
【发布时间】:2015-09-19 07:43:35
【问题描述】:

当运行启用了本机内存跟踪的 Java 应用程序(在 YARN 中)时(-XX:NativeMemoryTracking=detail 请参阅 https://docs.oracle.com/javase/8/docs/technotes/guides/vm/nmt-8.htmlhttps://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,提交=1190668KB) (线程#1154) (堆栈:保留=1185284KB,提交=1185284KB) (malloc=3809KB #5771) (竞技场=1575KB #2306) - 代码(保留=255744KB,提交=38384KB) (malloc=6144KB #8858) (mmap:保留=249600KB,提交=32240KB) - GC(保留=54995KB,提交=54995KB) (malloc=5775KB #217) (mmap:保留=49220KB,提交=49220KB) - 编译器(保留=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

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

【问题讨论】:

  • 没有。该问题和答案讨论了保留但未提交的内存以及说已提交和常驻交换之间的区别,我指出这里不是这种情况。
  • 答案还涵盖了已提交与常驻之间的差异。
  • 它包括一句话“已经被调出(或从未调入)的东西可以被提交内存但不是常驻”,这远远不足以帮助我理解我的情况。这是我明确表示的一个原因,但是,在这里被调出并不是一个充分的解释。我开始怀疑堆栈内存(与 JVM 堆不同)似乎是预先提交的而没有成为常驻内存,并且随着时间的推移,只有在实际堆栈使用的高水位线之前才成为常驻内存。对此进行确认或解释将是一个有用的答案。

标签: linux memory jvm hadoop-yarn


【解决方案1】:

我开始怀疑堆栈内存(与 JVM 堆不同)似乎是预先提交的而没有成为常驻内存,并且随着时间的推移,堆栈内存只会驻留到实际堆栈使用的高水位线。

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

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

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


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

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

Committed-but-not-backed-by-storage 内存已映射 - 例如 - PROT_READ | PROT_WRITE,但访问它仍会导致页面错误。但是该页面错误由内核静默处理,方法是使用实​​际内存支持它并返回执行,就好像什么都没发生一样。
即这是流程本身不会注意到的实现细节/优化。


对概念进行分解:

Used Heap:活动对象根据上次GC占用的内存量

已提交:已使用除 PROT_NONE 以外的其他内容映射的地址范围。由于延迟分配和分页,它们可能有也可能没有物理或交换支持。

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

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

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

【讨论】:

  • 通过 pmap -x 确认 NMT detail typcial 给出的线程范围在 1028 个中只有 88 个左右的 RSS。谢谢你的提示。你能解释一下 commit 的实际含义,以及如果它们都可以引用实际上尚未驻留/使用的虚拟内存,这与 reserved 有何不同?
  • 谢谢!非常有帮助。如果 JVM 本机内存跟踪实际上检查了 pmap -x 所做的数据并显示了实际驻留的数据,那就太棒了。
  • 您好,从 Reserved 到 Committed 的转换需要将 vma 的 prot 标志从 PROT_NONE 更改为 PROT_WRITEPROT_READ。但是,我没有发现任何系统调用可以做这样的事情。你能帮忙解释一下吗?
  • 感谢您这么快的回复。那么系统调用 mprotect: man7.org/linux/man-pages/man2/mprotect.2.html 呢?看来mproctect也可以改变vma的prot。
  • 如果可以的话,我会给 +100。这是对过去几年让我感到困惑的许多概念的绝妙解释。
猜你喜欢
  • 2020-12-21
  • 1970-01-01
  • 1970-01-01
  • 2020-07-01
  • 2011-12-01
  • 2016-11-30
  • 2015-02-17
  • 2015-05-21
  • 1970-01-01
相关资源
最近更新 更多