Java本地内存跟踪在“摘要”模式下的开销是多少?

14
我想知道启用NMT时,通过-XX:NativeMemoryTracking=summary的实际/典型开销是多少(我需要完整的命令选项:-XX:+UnlockDiagnosticVMOptions ‑XX:NativeMemoryTracking=summary ‑XX:+PrintNMTStatistics)。
无论是在SO、博客文章还是官方文档中,我都找不到太多信息。 文档中说:
注意:启用NMT会导致5%-10%的性能开销。
但他们没有说预期哪种模式会有这种性能开销(summarydetail都有吗?),以及这个开销到底是什么(CPU、内存等)。 在Native Memory Tracking guide中,他们声称:
启用NMT将导致JVM性能下降5-10%,并且所有malloc内存的NMT内存使用情况都会增加两个机器字作为malloc头。NMT内存使用情况也由NMT跟踪。
但同样,这对于summarydetail模式都有效吗?
我想要的基本上是,是否安全永久添加-XX:NativeMemoryTracking=summary到生产应用程序中(类似于连续的JFR记录),以及可能的成本。到目前为止,在我们的应用上测试这一点时,我没有发现任何区别,但很难。
是否有权威的信息来源包含更多关于这个性能开销的细节? 有人有启用此功能的生产应用程序的经验吗?
2个回答

16

免责声明:本回答基于JDK 18。我所写的大部分内容应该适用于旧版本。如有疑问,请自行测量。

背景:

NMT跟踪热点VM内存使用情况和通过直接字节缓冲器进行的内存使用情况。基本上,它钩取malloc/free和mmap/munmap调用并进行记账。

它没有跟踪其他JDK内存使用情况(热点VM之外)或第三方库使用情况。这一点很重要,因为它使得NMT的行为有些可预测。Hotspot试图避免通过malloc进行细粒度的分配。相反,它依赖于自定义的内存管理器,如Arenas、代码堆或Metaspace。

因此,对于大多数应用程序,hotspot的mallocs/mmaps不是那么“热门”,malloc的数量也不是那么大。

在本文的其余部分中,我将把重点放在malloc/free上,因为它们的数量远远超过了mmaps/munmaps:

内存成本:

  1. (detail+summary):每个malloc()两个单词
  2. (仅详细信息):Malloc站点表
  3. (detail+summary):VM映射和线程堆栈的映射列表

在这里,(1) 完全压倒了 (2) 和 (3)。因此,概述模式和详细模式之间的内存开销并不重要。

请注意,即使 (1) 可能也不那么重要,因为底层的libc分配器已经规定了一个最小的分配大小,可能比(纯分配大小+ 16字节malloc头)要大。因此,NMT内存开销实际上转化为RSS增加量的多少需进行测量。

由于JVM内存成本通常由堆大小主导,因此无法回答这意味着多少内存开销的总量。因此,将RSS与NMT成本进行比较几乎没有意义。但只是举个例子,对于具有1GB预先触碰堆的Spring PetClinic,NMT内存开销约为0.5%。

在我的经验中,NMT内存开销只在病态情况或引起JVM进行大量细粒度分配的角落情况下才会有影响。例如,如果执行大量类加载,则需要启用NMT以查看发生了什么。但通常这些都是你想启用NMT的情况。

性能成本:

NMT确实需要一些同步。它在概述模式下原子地增加每个malloc/free上的计数器。

在详细模式下,它做了更多的工作:

  • 捕获malloc站点上的调用堆栈
  • 在哈希映射中查找调用堆栈
  • 增加哈希映射条目计数器

这需要更多的周期。哈希映射是无锁的,但仍然使用原子操作进行修改。如果hotspot从不同的线程进行许多malloc,它看起来很昂贵。它真的有那么糟糕吗?

最坏情况
NMT off:        6   secs
NMT summary:    34  secs
NMT detail:     46  secs

看起来很疯狂。但实际上可能并不重要,因为这不是现实生活中的例子。

Spring宠物诊所启动,平均十次运行:

NMT off:               3.79 secs
NMT summary:           3.79 secs  (+0%)
NMT detail:            3.91 secs  (+3%)

所以,这里情况还不错。总结模式的成本实际上在测试噪音中消失了。

文艺复兴,哲学家基准

有趣的例子,因为它进行了大量的同步,导致许多对象监视器被膨胀,并且这些对象被动态分配内存:

平均基准分数:

NMT off:               4697
NMT summary:           4599  (-2%)
NMT detail:            4190  (-11%)

在两个其他示例之间。

结论

没有明确的答案。

内存和性能成本都取决于JVM进行了多少次分配。

对于正常的行为良好的应用程序,这个数量很小,但在病理情况(例如JVM错误)以及由用户程序引起的某些边缘情况场景中可能会很大,例如大量的类加载或同步。要真正确定,您需要自己进行测量。


1
非常感谢Thomas提供的所有细节和有趣的基准测试! - Juraj Martinka

10
Native Memory Tracking的开销显然取决于应用程序分配本地内存的频率。通常,在Java应用程序中,这并不是太频繁的事情,但情况可能有所不同。由于您已经尝试过,并且没有注意到性能差异,因此您的应用程序显然不是一个例外。
在“summary”模式下,Native Memory Tracking大致会执行以下操作:
- 将JVM中每个malloc请求的大小增加2个机器字(16字节); - 记录这2个字中的分配大小和标志; - 原子地增加(或者在free时减少)对应于给定内存类型的计数器; - 除了malloc和free之外,它还处理虚拟内存保留的变化和新区域的分配,但这些调用比malloc/free调用更不频繁。
因此,对我来说,开销非常小;5-10%绝对是一个很大的高估(这些数字对于详细模式是有意义的,该模式收集并存储堆栈跟踪信息,这是昂贵的,但是总结模式不会这样做)。
当许多线程同时分配/释放本机内存时,原子计数器的更新可能会成为瓶颈,但再次,这更像是一个极端情况。简而言之,如果您测量了真实的应用程序并没有注意到任何降级,则可以放心地在生产环境中启用NMT摘要。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接