如何理解“新”Java 飞行记录器 ObjectAllocationSample 事件?

Posted

技术标签:

【中文标题】如何理解“新”Java 飞行记录器 ObjectAllocationSample 事件?【英文标题】:How to make sense of the "new" Java flight recorder ObjectAllocationSample event? 【发布时间】:2021-10-26 06:37:06 【问题描述】:

在 OpenJDK 16.1 上分析我们的应用程序时,我看到 ObjectAllocationSample 事件中记录了一些奇怪的分配样本。

第一个示例事件是这个最初让我有点害怕的事件:

jdk.ObjectAllocationSample 
  startTime = 22:22:12.027
  objectClass = java.util.ArrayList (classLoader = bootstrap)
  weight = 6.1 GB
  eventThread = "My thread" (javaThreadId = 52)
  stackTrace = [
    jdk.jfr.internal.PlatformRecorder.getListeners() line: 169
    jdk.jfr.internal.PlatformRecording.notifyIfStateChanged(RecordingState, RecordingState) line: 496
    jdk.jfr.internal.PlatformRecording.start() line: 150
    jdk.jfr.Recording.start() line: 184
    myapp.JfrProfiler.start() line: 152
    ...
  ]

我很难相信 JFR 侦听器的列表有 6.1GB 大,并且该方法只调用了一次,那么在这种情况下,事件中的堆栈跟踪是什么意思呢? 这只是我们分配 ArrayList 的调用点之一,而且它恰好是开始录制后的第一个调用点吗? 如果是,是否在开始记录之前权重包括所有分配?

相同objectClass 的后续样本呢?我们能否相信堆栈跟踪可以很好地指示分配发生的位置,还是只是分配发生的随机位置?

诸如 JProfiler 之类的工具使用堆栈跟踪来提示哪些方法是分配热点,从而使上述调用站点显着突出,即使它不是大多数分配发生的地方。

【问题讨论】:

【参考方案1】:

该字段代表样本重量,而不是大小。

如果您要运行应用程序很长时间,这就是该调用站点/堆栈跟踪对分配压力的贡献程度。

为了保持低开销,采样器不会检测每个分配,在这种情况下发出的大小是合理的。

相反,它依赖于 JVM 内部的分配方式。为了避免一直使用“堆锁”,分配是在线程本地分配缓冲区 (TLAB) 中完成的。 TLAB 的大小由 JVM 确定,但通常会随着分配的增加而增加。它可以是几千字节到几兆字节。当线程用完 TLAB 时,它会进入一条缓慢的路径,在该路径中请求更多内存,可能会触发垃圾收集。此时,JVM 决定是否应该采样。

TLAB 的大小决定了样品的重量。如果很少分配某种类型的对象,则不太可能达到 TLAB 阈值。如果它经常分配,它很可能会达到采样点。此外,可以在 TLAB 之外分配大型数组,然后权重变为数组的大小。为了保持较低的开销,每秒发出多少个事件也有限制,默认为 150,因此权重可以是多次采样尝试的总和。

也就是说,6.1 GB 听起来相当高。对于每秒 150 个事件,这意味着大约 1000 GB/s 的分配速率。该事件是 JDK 16 的新事件,因此可能是错误、未初始化的内存或其他原因。您似乎也不太可能以如此高的分配率碰到只分配一次的对象。

【讨论】:

以上是关于如何理解“新”Java 飞行记录器 ObjectAllocationSample 事件?的主要内容,如果未能解决你的问题,请参考以下文章

ObjectA.Signal.connect(ObjectB.Slot) 不工作。我的理解正确吗?

为啥Java飞行记录仪采样太少?

读Java性能权威指南(第2版)笔记24_ Java飞行记录器JFR

Java Mission Control:飞行记录器抛出:javax.naming.ServiceUnavailableException

Flink 使用另一个 ObjectA 流中的 List<ObjectB> 创建新的 ObjectB 流

如何创建可重用的即时(持续时间)Java Flight Recorder JFR 事件?