Backend Development 9 min read

How to Diagnose and Fix Native Memory Leaks in Java Applications

This guide explains why Java heap dumps miss native memory, introduces Native Memory Tracking (NMT) and tools like pmap and gdb, and provides practical code and JVM settings to detect, analyze, and prevent native memory leaks in Java programs.

Big Data Technology Tribe
Big Data Technology Tribe
Big Data Technology Tribe
How to Diagnose and Fix Native Memory Leaks in Java Applications

When a Java application suffers from native memory leaks, a regular heap dump (e.g., via

jmap

) will not capture the native allocations, making analysis difficult; for example, a process may show 20 GB RSS while the heap dump is only 2 GB.

This article summarizes the steps to investigate native memory leaks.

NMT (Native Memory Tracking)

Native Memory Tracking is a JVM feature that monitors native memory allocation and usage, helping to identify leaks and excessive consumption.

1.1 Enable NMT

Add the following JVM option:

-XX:NativeMemoryTracking=detail

Ways to add it:

1.1.1 Command line

java -XX:NativeMemoryTracking=detail -jar YourApp.jar

1.1.2 Maven/Gradle

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
                <argLine>-XX:NativeMemoryTracking=detail</argLine>
            </configuration>
        </plugin>
    </plugins>
</build>

1.2 Define a baseline

Run the following command to set a baseline for later comparison:

jcmd <PID> VM.native_memory baseline scale=MB

1.3 Capture memory details

Take a snapshot of native memory and write it to a file:

jcmd <PID> VM.native_memory detail scale=MB > native_memory_detail

The

native_memory_detail

file contains entries such as reserved (memory reserved by the OS) and committed (currently used memory).

1.4 Capture memory diff

Periodically capture the diff to see which components increase committed memory:

jcmd <PID> VM.native_memory detail.diff scale=MB > native_memory_diff

The diff output shows lines like

Total: reserved=3111MB +521MB, committed=1401MB +842MB

, where the

+521MB

indicates the growth compared with the baseline.

After identifying the culprit, use

jcmd <PID> help

to explore more options, e.g., classloader statistics:

jcmd <PID> VM.classloader_stats
jcmd <PID> VM.class_hierarchy

pmap + gdb analysis

Use

pmap -x [PID]

to view memory usage of the Java process. Sample output columns:

Address          Kbytes   RSS   Dirty Mode  Mapping
0000000000400000   4       4      0   r-xp  /usr/bin/pmap
...
00007f8b00000000 65512   40148 40148 rwx-- [ anon ]

Columns meaning:

Address – start address of the memory region

Kbytes – size of the region in KB

RSS – resident set size (actual physical memory used)

Dirty – number of dirty pages

Mode – permissions (r=read, w=write, x=exec, s=shared, p=private)

Mapping – type or filename of the mapping

Dump a specific region with gdb:

gdb -p <pid>
(gdb) dump memory mem.bin 0x00007f8b00000000 0x00007f8b00000000+65512

Extract readable strings:

cat mem.bin | strings

How to avoid native memory leaks

3.1 Properly manage Direct Memory

public class DirectMemoryManager {
    private static final long MAX_DIRECT_MEMORY = 1024 * 1024 * 1024; // 1GB
    private static final AtomicLong usedDirectMemory = new AtomicLong(0);

    public static ByteBuffer allocateDirect(int size) {
        long current = usedDirectMemory.get();
        if (current + size > MAX_DIRECT_MEMORY) {
            throw new OutOfMemoryError("Direct memory limit exceeded");
        }
        ByteBuffer buffer = ByteBuffer.allocateDirect(size);
        usedDirectMemory.addAndGet(size);
        return buffer;
    }

    public static void releaseDirect(ByteBuffer buffer) {
        if (buffer.isDirect()) {
            usedDirectMemory.addAndGet(-buffer.capacity());
            buffer.clear();
        }
    }
}

3.2 Use a resource pool

public class DirectBufferPool {
    private final Queue<ByteBuffer> pool = new ConcurrentLinkedQueue<>();
    private final int bufferSize;
    private final int maxPoolSize;

    public DirectBufferPool(int bufferSize, int maxPoolSize) {
        this.bufferSize = bufferSize;
        this.maxPoolSize = maxPoolSize;
    }

    public ByteBuffer acquire() {
        ByteBuffer buffer = pool.poll();
        if (buffer == null) {
            buffer = ByteBuffer.allocateDirect(bufferSize);
        }
        return buffer;
    }

    public void release(ByteBuffer buffer) {
        if (buffer != null && buffer.isDirect() && pool.size() < maxPoolSize) {
            buffer.clear();
            pool.offer(buffer);
        }
    }
}

3.3 Enable NMT monitoring (with performance cost)

Continuously run NMT to spot memory growth.

3.4 Periodic cleanup

public class NativeMemoryCleaner {
    private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

    public static void startPeriodicCleanup() {
        scheduler.scheduleAtFixedRate(() -> {
            System.gc(); // trigger GC to free Direct Memory
            try {
                Field cleanerField = Class.forName("java.nio.DirectByteBuffer").getDeclaredField("cleaner");
                cleanerField.setAccessible(true);
                // custom cleanup logic can be added here
            } catch (Exception e) {
                // handle exception
            }
        }, 0, 5, TimeUnit.MINUTES);
    }
}

3.5 Tune JVM parameters

# Limit Direct Memory size
-XX:MaxDirectMemorySize=1g
# Enable detailed GC logs
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps
# Enable NMT
-XX:NativeMemoryTracking=detail
# Set reasonable thread stack size
-Xss256k

3.6 Use monitoring tools

Leverage JMX or other observability platforms to track native memory usage in real time.

JavaJVMPerformancememory leakNative Memory Tracking
Big Data Technology Tribe
Written by

Big Data Technology Tribe

Focused on computer science and cutting‑edge tech, we distill complex knowledge into clear, actionable insights. We track tech evolution, share industry trends and deep analysis, helping you keep learning, boost your technical edge, and ride the digital wave forward.

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.