Unlock Hidden JVM Memory: Track Native Allocations with NMT

This tutorial explains why Java applications often exceed their -Xmx limits, enumerates the main sources of native memory consumption in the JVM, and shows how to monitor and analyze these allocations using Native Memory Tracking (NMT) commands and flags.

Programmer DD
Programmer DD
Programmer DD
Unlock Hidden JVM Memory: Track Native Allocations with NMT

1. Overview

Ever wondered why a Java application using the well‑known -Xms and -Xmx flags consumes more memory than the specified limits? The JVM can allocate additional native memory for various reasons, causing the actual consumption to exceed the -Xmx ceiling. This tutorial lists common native memory sources in the JVM, the tuning flags that control their size, and how to monitor them with native memory tracking.

2. Native Allocations

Besides the heap, the JVM allocates sizable native memory blocks for class metadata, application code, JIT‑generated code, internal data structures, and more.

2.1 Metaspace

The JVM uses a dedicated non‑heap region called Metaspace (formerly PermGen) to store metadata about loaded classes. Its size is independent of the heap and is controlled by flags such as -XX:MetaspaceSize and -XX:MaxMetaspaceSize (or -XX:PermSize and -XX:MaxPermSize on Java 8 and earlier).

2.2 Threads

Each thread has its own stack, typically about 1 MB on modern 64‑bit OSes, configurable via -Xss. The total stack memory can become unbounded if the number of threads is not limited.

2.3 Code Cache

The JIT compiler stores compiled machine code in a non‑heap region called the Code Cache. Its size is controlled by -XX:InitialCodeCacheSize and -XX:ReservedCodeCacheSize.

2.4 Garbage Collection

All GC algorithms use additional native data structures, which also consume native memory.

2.5 Symbols

String interning stores a single copy of each distinct string in a native hash table (the String Table). The size of this table can be tuned with -XX:StringTableSize. The runtime constant pool, which holds literals and method/field references, also resides in native memory.

2.6 Native Byte Buffers

Developers can allocate native memory directly via JNI malloc or NIO direct ByteBuffer s.

2.7 Additional Tuning Flags

Various other -XX flags control specific native memory areas; you can discover them with commands like:

$ java -XX:+PrintFlagsFinal -version | grep <concept>

3. Native Memory Tracking (NMT)

Enable NMT with the flag -XX:NativeMemoryTracking=off|summary|detail. By default it is off; set it to summary or detail to view aggregated or detailed reports.

Example for a typical Spring Boot application:

$ java -XX:NativeMemoryTracking=summary -Xms300m -Xmx300m -XX:+UseG1GC -jar app.jar

Use jcmd <pid> VM.native_memory to obtain a snapshot. Find the PID with jps -l:

$ jps -l
7858 app.jar // This is our app
7899 sun.tools.jps.Jps

Sample output sections:

3.1 Total Allocation

Native Memory Tracking:
Total: reserved=1731124KB, committed=448152KB

Reserved memory is the maximum the JVM might use; committed memory is what is currently in use.

3.2 Heap

Java Heap (reserved=307200KB, committed=307200KB)
    (mmap: reserved=307200KB, committed=307200KB)

3.3 Metaspace

Class (reserved=1091407KB, committed=45815KB)
    (classes #6566)
    (malloc=10063KB #8519)
    (mmap: reserved=1081344KB, committed=35752KB)

3.4 Threads

Thread (reserved=37018KB, committed=37018KB)
    (thread #37)
    (stack: reserved=36864KB, committed=36864KB)
    (malloc=112KB #190)
    (arena=42KB #72)

3.5 Code Cache

Code (reserved=251549KB, committed=14169KB)
    (malloc=1949KB #3424)
    (mmap: reserved=249600KB, committed=12220KB)

3.6 GC

GC (reserved=61771KB, committed=61771KB)
    (malloc=17603KB #4501)
    (mmap: reserved=44168KB, committed=44168KB)

Switching to Serial GC reduces GC native memory to about 1 MB:

$ java -XX:NativeMemoryTracking=summary -Xms300m -Xmx300m -XX:+UseSerialGC -jar app.jar
GC (reserved=1034KB, committed=1034KB)
    (malloc=26KB #158)
    (mmap: reserved=1008KB, committed=1008KB)

3.7 Symbols

Symbol (reserved=10148KB, committed=10148KB)
    (malloc=7295KB #66194)
    (arena=2853KB #1)

3.8 Tracking Over Time

Set a baseline:

$ jcmd <pid> VM.native_memory baseline
Baseline succeeded

Later compare with:

$ jcmd <pid> VM.native_memory summary.diff

The diff shows how reserved and committed memory changed.

3.9 Detailed NMT

For a fully detailed map, enable -XX:NativeMemoryTracking=detail.

4. Conclusion

This article enumerated the various native memory consumers inside the JVM and demonstrated how to inspect a running application with Native Memory Tracking, enabling more effective tuning of both the application and its runtime environment.

Source: http://www.spring4all.com/article/15116
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.

javaJVMperformanceMemory ManagementNative Memory Tracking
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

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.