How GraalVM Native Image’s New Native Memory Tracking Helps Diagnose Memory Usage

GraalVM Native Image now supports Native Memory Tracking (NMT), allowing developers to monitor off‑heap memory allocations, compare Java and native execution, integrate with JFR, and assess performance impact through detailed reports and experimental events.

Java Architecture Diary
Java Architecture Diary
Java Architecture Diary
How GraalVM Native Image’s New Native Memory Tracking Helps Diagnose Memory Usage

1. Background

The term "native memory" (sometimes called off‑heap or unmanaged memory) refers to any memory not allocated on the Java heap. In a traditional JVM, native memory includes memory used by the JVM itself and memory allocated via Unsafe#allocateMemory(long) or external libraries. In a Native Image, the SubstrateVM is written in Java, so most memory is allocated on the Java heap, but some components still use native memory outside the heap, which NMT aims to track.

2. Use Cases

NMT is useful because it reveals memory usage that heap dumps cannot show. For example, when an application's RSS is unexpectedly high, a heap dump may not explain the excess, indicating native memory as the culprit. NMT helps identify whether code changes or SubstrateVM runtime changes caused the extra native memory usage.

While most SubstrateVM memory is on the Java heap and visible in heap dumps, components such as garbage collection and JDK Flight Recorder still allocate native memory, making NMT valuable for comparing regular Java and Native Image performance.

3. Using NMT in Native Image

Enable NMT at build time with the flag --enable-monitoring=nmt. By default NMT is not included in the image. native-image --enable-monitoring=nmt MyApp At runtime, add -XX:+PrintNMTStatistics to dump the NMT report when the program exits. ./myapp -XX:+PrintNMTStatistics Sample report (truncated):

Native memory tracking
  Peak total used memory: 13632010 bytes
  Total alive allocations at peak usage: 1414
  Total used memory: 2125896 bytes
  ...
  Unsafe currently used memory: 2099240 bytes
  Unsafe currently alive allocations: 36

The report shows instantaneous memory usage, counts, and peaks for each category, which differ from HotSpot categories.

4. NMT JFR Events

Native Image supports the OpenJDK JFR events jdk.NativeMemoryUsage and jdk.NativeMemoryUsageTotal. The former reports usage per category, while the latter reports total usage; neither includes count information.

Two additional custom events expose peak data: jdk.NativeMemoryUsagePeak and jdk.NativeMemoryUsageTotalPeak. In regular Java, users would use jcmd to obtain peak usage, but jcmd is not supported in Native Image.

4.1 Enabling JFR and NMT

Build with both JFR and NMT enabled: native-image --enable-monitoring=nmt,jfr MyApp Start the application with Flight Recording:

./myapp -XX:StartFlightRecording=filename=recording.jfr

4.2 Example

Using the jfr tool to print the jdk.NativeMemoryUsage event:

jfr print --events jdk.NativeMemoryUsage recording.jfr

Output example:

jdk.NativeMemoryUsage {
  startTime = 15:47:33.392 (2024-04-25)
  type = "JFR"
  reserved = 10.1 MB
  committed = 10.1 MB
}
...

4.3 Showing Experimental JFR Events

These events are marked experimental and are hidden by default in VisualVM and JDK Mission Control. To display them, enable "Display experimental items" in VisualVM's Browser tab, or select "Include experimental events..." in JDK Mission Control preferences.

5. Implementation Details

NMT works by instrumenting calls to malloc, calloc, realloc, and mmap. The instrumentation adds a small header to each allocation, storing metadata that allows the NMT system to maintain an accurate view of native memory blocks. The recorded usage consists of a series of continuously updated counters, and reports provide a snapshot of native memory at the moment of generation.

6. Performance Impact

The overhead of NMT is minimal in most scenarios—approximately 16 bytes per allocation for the header. Benchmarks with a simple Quarkus native image show negligible impact on RSS, startup time, and response latency, while the image size grows by less than 3 KB.

Measurement                With NMT (NI)   Without NMT (NI)   With NMT (Java)   Without NMT (Java)
RSS (KB)                  53,030          53,390              151,460           148,952
Startup time (ms)         147             144                 1,378             1,364
Avg response (us)         4,040           3,766               4,881             4,613
...

When JFR is also enabled, the performance impact becomes more noticeable, as shown in a second set of measurements.

Measurement                With NMT+JFR (NI)   Without NMT+JFR (NI)   With NMT+JFR (Java)   Without NMT+JFR (Java)
RSS (KB)                  72,366               52,388                191,504            149,756
Startup time (ms)         193                  138                   1,920              1,315
Avg response (us)         5,038                4,451                 5,990              4,452
...

7. Limitations and Roadmap

Current NMT only tracks malloc / calloc / realloc allocations; virtual‑memory tracking is not yet integrated. Calls made directly to malloc from native libraries bypass NMT. Native Image also lacks a way to dump NMT reports at arbitrary points without JFR; a signal‑based dump is under investigation.

OpenJDK offers two NMT modes—summary (implemented) and detailed (not yet supported in Native Image). Counters are updated non‑atomically, which can cause temporary inconsistencies between total and peak measurements.

8. Conclusion

The newly added NMT feature in Native Image gives users visibility into native memory consumption, complementing JFR, JMX, heap dumps, and debugging information. Users are encouraged to try the feature and provide feedback via GraalVM’s GitHub issues.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

performancegraalvmnative-imageJFRNative Memory Tracking
Java Architecture Diary
Written by

Java Architecture Diary

Committed to sharing original, high‑quality technical articles; no fluff or promotional content.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.