原生内存跟踪 (NMT) 与原生镜像

原生内存跟踪 (NMT) 是一种可服务性功能,用于记录应用程序的非堆内存使用情况。术语“非堆内存”有时与“原生内存”或“非托管内存”互换使用。这实质上意味着任何不受垃圾收集器管理的内存。

与 HotSpot JVM 不同,原生镜像主要使用其垃圾收集器管理的收集堆上的内存。但是,原生镜像仍然在许多地方使用原生内存以避免在托管堆上进行分配。一些示例包括 JFR、垃圾收集器和堆转储。原生内存也可以在应用程序级别使用 Unsafe#allocateMemory(long) 直接请求。

启用原生内存跟踪 #

NMT 支持默认情况下处于禁用状态,必须在构建时显式启用。

要构建具有 NMT 的原生可执行文件,请使用 --enable-monitoring=nmt 选项。如果在构建时包含 NMT,它将在运行时始终启用。这与 HotSpot 不同,HotSpot 允许在运行时启用/禁用 NMT。

native-image --enable-monitoring=nmt YourApplication

从原生可执行文件启动应用程序时添加 -XX:+PrintNMTStatistics 会告诉 NMT 在应用程序完成时将报告写入标准输出。

./yourapplication -XX:+PrintNMTStatistics

性能 #

在原生镜像上,NMT 的 CPU 和内存消耗都非常少。与 JFR 等其他可服务性功能相比,NMT 的开销相对较小。

用于 NMT 的 JFR 事件 #

原生镜像支持 OpenJDK JFR 事件 jdk.NativeMemoryUsagejdk.NativeMemoryUsageTotal

还有两个特定的原生镜像 JFR 事件,您可以访问:jdk.NativeMemoryUsagePeakjdk.NativeMemoryUsageTotalPeak。这些特定于原生镜像的事件是专门为公开峰值使用数据而创建的,这些数据无法通过从 OpenJDK 移植过来的 JFR 事件公开。这些新事件被标记为实验性。您可能需要在 JDK Mission Control 等软件中启用实验性事件才能查看它们。

要将这些 JFR 事件用于 NMT,请在调用 native-image 工具时传递 --enable-monitoring=jfr,nmt 选项来启用 JFR 监控,然后在运行时启动 JFR 记录。(在 原生镜像的 JDK 飞行记录器 (JFR) 中了解更多信息)。

以下是在使用 jfr 命令行工具查看时新事件的示例

jfr print --events jdk.NativeMemoryUsagePeak recording.jfr 

jdk.NativeMemoryUsagePeak {
  startTime = 13:18:50.605 (2024-04-30)
  type = "Threading"
  peakReserved = 424 bytes
  peakCommitted = 424 bytes
  countAtPeak = 4
  eventThread = "JFR Shutdown Hook" (javaThreadId = 63)
}

jdk.NativeMemoryUsagePeak {
  startTime = 13:18:50.605 (2024-04-30)
  type = "Unsafe"
  peakReserved = 14.0 kB
  peakCommitted = 14.0 kB
  countAtPeak = 2
  eventThread = "JFR Shutdown Hook" (javaThreadId = 63)
}

限制 #

在 HotSpot 上,NMT 有两种模式:摘要模式和详细模式。在原生镜像中,当前仅支持 NMT 摘要模式。详细模式(启用调用点跟踪)不可用。捕获基线目前也不可能。如果您有兴趣支持这些附加功能,请向 GitHub 上的 GraalVM 项目 提交请求。

Malloc 跟踪是目前唯一可用的功能(截至适用于 JDK 23 的 GraalVM)。

原生镜像与 HotSpot 相同,只能跟踪 VM 级别以及使用 Unsafe#allocateMemory(long) 进行的分配。例如,如果库代码或应用程序代码直接调用 malloc,则该调用将绕过 NMT 统计信息并不会被跟踪。

进一步阅读 #

联系我们