原生内存跟踪 (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 Flight Recorder (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 跟踪是目前唯一可用的功能(截至 GraalVM for JDK 23)。

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

延伸阅读 #

联系我们