Backend Development 17 min read

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.

<code>native-image --enable-monitoring=nmt MyApp</code>

At runtime, add

-XX:+PrintNMTStatistics

to dump the NMT report when the program exits.

<code>./myapp -XX:+PrintNMTStatistics</code>

Sample report (truncated):

<code>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
</code>

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:

<code>native-image --enable-monitoring=nmt,jfr MyApp</code>

Start the application with Flight Recording:

<code>./myapp -XX:StartFlightRecording=filename=recording.jfr</code>

4.2 Example

Using the

jfr

tool to print the

jdk.NativeMemoryUsage

event:

<code>jfr print --events jdk.NativeMemoryUsage recording.jfr</code>

Output example:

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

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.

<code>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
...</code>

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

<code>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
...</code>

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.

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

login 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.