A Java library that collects JVM Native Memory Tracking (NMT) data via jcmd and exposes it as metrics through Micrometer and/or OpenTelemetry.
NMT provides visibility into the JVM's native memory usage beyond the Java heap -- thread stacks, GC overhead, compiled code, class metadata, and more. This library makes that data available as standard metrics for your monitoring stack.
The JVM must be started with Native Memory Tracking enabled:
-XX:NativeMemoryTracking=summary
The jcmd tool must be available on the system (it's part of the JDK). If you're using a custom JRE built with jlink, make sure to include the jdk.jcmd module.
Minimum supported Java is 17.
Pick the module matching your metrics API:
| Metrics API | Artifact |
|---|---|
| Micrometer | org.framefork:nmt-micrometer |
| OpenTelemetry | org.framefork:nmt-opentelemetry |
| Core only | org.framefork:nmt-core |
Find the latest version in this project's GitHub releases or on Maven Central.
implementation("org.framefork:nmt-micrometer:${version}")
// or
implementation("org.framefork:nmt-opentelemetry:${version}")<dependency>
<groupId>org.framefork</groupId>
<artifactId>nmt-micrometer</artifactId>
<version>${version}</version>
</dependency>Both nmt-micrometer and nmt-opentelemetry include Spring Boot auto-configuration that activates automatically when all conditions are met:
- The corresponding metrics API is on the classpath (
MeterRegistryorOpenTelemetrybean) - NMT is enabled on the JVM (
-XX:NativeMemoryTracking=summary) - The
jcmdexecutable is available
No additional configuration is needed. Just add the dependency and the NMT flag.
var collector = new CachingNmtDataCollector(
new JcmdNmtDataCollector(new DefaultJcmdRunner())
);
var binder = new NmtMeterBinder(collector);
binder.bindTo(meterRegistry);var collector = new CachingNmtDataCollector(
new JcmdNmtDataCollector(new DefaultJcmdRunner())
);
var metrics = new NmtOpenTelemetryMetrics(openTelemetry, collector);
// On shutdown:
metrics.close();Both modules register the same two gauges for each NMT memory category:
| Metric name | Description | Unit |
|---|---|---|
jvm.memory.nmt.committed |
Committed memory (physical/swap memory in use) | bytes |
jvm.memory.nmt.reserved |
Reserved memory (virtual address space reserved) | bytes |
Each metric is tagged/attributed with category identifying the NMT memory type (e.g., java_heap, gc, thread, code, class, compiler, metaspace, etc.).
The categories are extracted dynamically from jcmd output, so new categories added in future JDK versions will be picked up automatically without library changes.
| Spring Boot | Micrometer | OpenTelemetry API | Status |
|---|---|---|---|
| 3.2.x | 1.12.x | 1.31.x | Tested |
| 3.3.x | 1.13.x | 1.38.x | Tested |
| 3.4.x | 1.14.x | 1.43.x | Tested |
| 3.5.x | 1.15.x | 1.49.x | Tested |
| 4.0.x | 1.16.x | 1.55.x | Tested |
| Module | Description |
|---|---|
nmt-core |
jcmd runner, NMT output parser, data model, caching |
nmt-micrometer |
Micrometer MeterBinder integration + Spring Boot auto-configuration |
nmt-opentelemetry |
OpenTelemetry gauge registration + Spring Boot auto-configuration |
- The library runs
jcmd <pid> VM.native_memory summary scale=bas a subprocess - The text output is parsed with regex into structured data (category name, reserved bytes, committed bytes)
- Results are cached with a 5-second TTL to avoid spawning
jcmdtoo frequently - Metric callbacks read from the cache, which is refreshed on the next collection cycle
The jcmd <pid> VM.native_memory summary scale=b command produces text output like:
Native Memory Tracking:
Total: reserved=1607492304, committed=196489936
malloc: 30716624 #15376
mmap: reserved=1576775680, committed=165773312
- Java Heap (reserved=104857600, committed=104857600)
(mmap: reserved=104857600, committed=104857600)
- Class (reserved=1073862359, committed=317143)
(classes #1093)
...
- Thread (reserved=23194136, committed=1092120)
...
The parser dynamically extracts category names and values using regex, so it is forward-compatible with new JDK versions that may introduce additional categories.
The following categories are typically reported by the JVM (varies by JDK version):
| jcmd label | Metric tag value | Description |
|---|---|---|
Total |
total |
Sum of all categories |
Java Heap |
java_heap |
Heap memory |
Class |
class |
Class metadata |
Thread |
thread |
Thread stacks and structures |
Code |
code |
Generated/compiled code |
GC |
gc |
Garbage collector data structures |
GCCardSet |
gccard_set |
G1 card set remembered set |
Compiler |
compiler |
JIT compiler |
Internal |
internal |
VM internal use |
Symbol |
symbol |
String table, symbol table |
Native Memory Tracking |
native_memory_tracking |
NMT's own overhead |
Shared class space |
shared_class_space |
CDS archive |
Arena Chunk |
arena_chunk |
Arena allocations |
Tracing |
tracing |
Tracing infrastructure |
Logging |
logging |
Logging infrastructure |
Arguments |
arguments |
VM arguments |
Module |
module |
Module system |
Safepoint |
safepoint |
Safepoint infrastructure |
Synchronization |
synchronization |
Synchronization primitives |
Metaspace |
metaspace |
Metaspace memory |
String Deduplication |
string_deduplication |
G1 string deduplication |
Object Monitors |
object_monitors |
Object monitors |
Category labels are converted to lowercase snake_case for metric tag/attribute values. New categories added in future JDK versions are picked up automatically.
Reference: OpenJDK memory types in allocation.hpp
Since multiple metric callbacks fire independently during a single collection cycle (one per category per metric = ~46 calls), jcmd should only be called once per interval. The CachingNmtDataCollector stores the parsed result with a 5-second TTL. When any metric callback fires, it reads from the cache and only runs jcmd if the cache has expired.
- Container shutdown (exit code 143): When the container is shutting down,
jcmdmay return exit code 143 (SIGTERM). This is caught and logged as a warning, and empty values are returned. - jcmd failures: Other errors are logged and result in empty metrics (zeros) rather than exceptions propagating to the metrics system.
- NMT not enabled: If NMT is not enabled,
jcmdreturns"Native memory tracking is not enabled". The parser handles this gracefully and returns an empty summary.
If you're building a custom JRE image with jlink, make sure to include the jdk.jcmd module:
jlink --add-modules ...,jdk.jcmd --output /opt/jre
Without this module, jcmd will not be available and the library will not activate (the Spring Boot auto-configuration checks for jcmd availability).