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

Jur*_*nka 14 java memory jvm

我想知道通过启用 NMT 时的真实/典型开销是多少\xe2\x80\x91XX:NativeMemoryTracking=summary(我之后的完整命令选项是-XX:+UnlockDiagnosticVMOptions \xe2\x80\x91XX:NativeMemoryTracking=summary \xe2\x80\x91XX:+PrintNMTStatistics

\n

我在任何地方都找不到太多信息 - 无论是在 SO、博客文章还是官方文档上。\n文档说:

\n
\n

注意:启用NMT会导致5%-10%的性能开销

\n
\n

但他们没有说明哪种模式预计会有这种性能开销(摘要详细信息?)\n以及这种开销到底是什么(CPU、内存……)。\n在本机内存跟踪指南中,他们声称:

\n
\n

启用 NMT 将导致 JVM 性能下降 5-10%,并且 NMT 的内存使用会向所有 malloc 内存添加 2 个机器字作为 malloc 标头。NMT 内存使用情况也会被 NMT 跟踪。

\n
\n

但同样,对于摘要模式详细模式来说都是如此吗?

\n

我所追求的基本上是为生产应用程序永久添加是否安全\xe2\x80\x91XX:NativeMemoryTracking=summary(类似于连续JFR 记录)以及潜在成本是多少。\n到目前为止,在我们的应用程序上测试这一点时,我没有发现差异但很难

\n

是否有包含有关此性能开销的更多详细信息的权威信息源?\n是否有人有为生产应用程序永久启用此功能的经验?

\n

Tho*_*efe 16

免责声明:我的答案基于 JDK 18。我编写的大部分内容应该对旧版本有效。如有疑问,您需要自己测量。

背景:

NMT 跟踪热点 VM 内存使用情况和通过直接字节缓冲区完成的内存使用情况。基本上,它连接到 malloc/free 和 mmap/munmap 调用并进行记帐。

它不跟踪其他 JDK 内存使用情况(热点 VM 之外)或第三方库的使用情况。这在这里很重要,因为它使 NMT 的行为在某种程度上是可预测的。Hotspot 尝试避免通过 malloc 进行细粒度分配。相反,它依赖于自定义内存管理器,例如 Arenas、代码堆或元空间。

因此,对于大多数应用程序来说,来自热点的 malloc/mmap 并不是那么“热门”,并且 malloc 的数量也不是那么大。

在本文的其余部分中,我将重点关注 malloc/free,因为它们的数量迄今为止超过了 mmap/munmap 的数量:

内存成本:

  1. (详细+摘要):每个 malloc() 2 个字
  2. (仅详细信息):Malloc 站点表
  3. (详细+摘要):虚拟机映射和线程堆栈的映射列表

在这里,(1)完全使(2)和(3)相形见绌。因此,摘要模式和详细模式之间的内存开销并不大。

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

这意味着总共有多少内存开销无法回答,因为 JVM 内存成本通常由堆大小决定。因此,将 RSS 与 NMT 成本进行比较几乎没有意义。但仅举个例子,对于具有 1GB 预修饰堆 NMT 内存的 Spring Petclinic,开销约为 0.5%。

根据我的经验,NMT 内存开销仅在病态情况或导致 JVM 执行大量细粒度分配的极端情况下才重要。例如,如果一个人进行了大量的类加载。但通常在这些情况下,您希望启用 NMT,以查看发生了什么。

性能成本:

NMT 确实需要一些同步。它以摘要模式自动增加每个 malloc/free 上的计数器。

在详细模式下,它可以执行更多操作:

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

这需要更多的周期。哈希图是无锁的,但仍然通过原子操作进行修改。它看起来很昂贵,特别是当热点从不同线程进行许多 malloc 时。到底有多糟糕?

最坏情况的例子

微基准测试,在 24 核机器上由 100 个并发线程完成 64 mio malloc 分配(通过 Unsafe.allocateMemory()):

NMT off:        6   secs
NMT summary:    34  secs
NMT detail:     46  secs
Run Code Online (Sandbox Code Playgroud)

这看起来很疯狂。然而,在实践中这可能并不重要,因为这不是现实生活中的例子。

春季宠物诊所启动,十次运行的平均值:

NMT off:               3.79 secs
NMT summary:           3.79 secs  (+0%)
NMT detail:            3.91 secs  (+3%)
Run Code Online (Sandbox Code Playgroud)

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

文艺复兴,哲学家的标杆

有趣的例子,因为这做了很多同步,导致许多对象监视器膨胀,并且这些对象监视器被分配:

平均基准分数:

NMT off:               4697
NMT summary:           4599  (-2%)
NMT detail:            4190  (-11%)
Run Code Online (Sandbox Code Playgroud)

介于其他两个示例之间。

结论

没有明确的答案。

内存和性能成本都取决于 JVM 执行的分配次数。

对于正常表现良好的应用程序来说,这个数字很小,但在病态情况(例如 JVM bug)以及由用户程序引起的某些极端情况下(例如,大量类加载或同步),这个数字可能很大。要真正确定,您需要衡量一下自己。


apa*_*gin 11

本机内存跟踪的开销显然取决于应用程序分配本机内存的频率。通常,这在 Java 应用程序中并不常见,但情况可能有所不同。由于您已经尝试过并且没有注意到性能差异,因此您的应用程序显然也不例外。

在该summary模式下,Native Memory Tracking大致做了以下几件事:

  • malloc将JVM 中的每个请求增加2 个机器字(16 字节);
  • 在这2个字中记录分配大小和标志;
  • 以原子方式递增(或递减free)与给定内存类型对应的计数器;
  • 除了malloc和之外free,它还处理虚拟内存预留的更改和新区域的分配,但这些甚至比malloc/free调用更不频繁。

所以,对我来说,开销相当小;5-10% 绝对是一个很大的高估(这些数字对于收集和存储堆栈跟踪的模式来说是有意义的detail,这种模式很昂贵,但summary并不能做到这一点)。

当许多线程同时分配/释放本机内存时,原子计数器的更新可能成为瓶颈,但同样,这更像是一种极端情况。简而言之,如果您测量了真实的应用程序并且没有发现任何性能下降,那么您可能可以安全地在生产中启用 NMT 摘要。