原生内存跟踪 (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.NativeMemoryUsage
和 jdk.NativeMemoryUsageTotal
。
还有两个特定的原生镜像 JFR 事件,您可以访问:jdk.NativeMemoryUsagePeak
和 jdk.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 统计信息并不会被跟踪。