Why Did My Spring Boot Service Consume 7 GB? Uncovering Native Memory Leaks

The article details a step‑by‑step investigation of a Spring Boot application that unexpectedly used 7 GB of physical memory despite a 4 GB heap limit, revealing how native memory allocations, the Inflater class, and glibc memory pools caused off‑heap leaks and how proper configuration and library updates resolved the issue.

Programmer DD
Programmer DD
Programmer DD
Why Did My Spring Boot Service Consume 7 GB? Uncovering Native Memory Leaks

After migrating a project to the MDP framework (based on Spring Boot), the system repeatedly reported high Swap usage. Although the JVM was configured with a 4 GB heap, the actual physical memory consumption reached 7 GB.

1. Locate memory regions at the Java level

Adding the -XX:NativeMemoryTracking=detail JVM parameter and restarting the project allowed jcmd <pid> VM.native_memory detail to show memory distribution, revealing that the committed memory reported by the command was lower than the physical usage because it omitted native allocations made by unsafe or DirectByteBuffer.

Further inspection with pmap showed many 64 MB address spaces not accounted for by jcmd, indicating native memory usage.

2. Use system‑level tools to locate off‑heap memory

Since Java‑level tools could not pinpoint the source, system tools were employed.

Using gperftools

gperftools was used to monitor memory allocation. The monitoring screenshot showed that malloc‑allocated memory peaked at 3 GB before being released, then stabilized around 700–800 MB.

Tracing system calls with strace

Running strace -f -e brk,mmap,munmap -p <pid> during startup captured memory‑request system calls, confirming numerous 64 MB mmap allocations.

Dumping suspicious memory with GDB

Using gdb -p <pid> and dump memory mem.bin startAddr endAddr, then inspecting the dump with strings, revealed that the memory contained decompressed JAR data, suggesting that the Inflater used during JAR scanning was allocating off‑heap memory.

Why was off‑heap memory not released?

Why did the old framework not exhibit the problem?

Why didn’t the off‑heap memory get freed?

Why were the memory blocks all 64 MB, far larger than any JAR?

Why did gperftools report only ~700 MB while the OS showed much higher usage?

Investigation of Spring Boot’s ZipInflaterInputStream showed that it wrapped Inflater without explicitly releasing the native memory; it relied on the finalize method of Inflater to free memory during GC. However, glibc’s memory pool retained the released pages, making it appear as a leak.

Modifying the MCC (Meituan Configuration Center) scan path to limit JAR scanning eliminated the excessive off‑heap allocations. Newer Spring Boot versions (2.0.5.RELEASE) have already fixed the issue by actively releasing the native memory.

Summary

The root cause was the default MCC configuration scanning all JARs, causing Spring Boot’s Inflater to allocate large off‑heap buffers that were only freed via GC finalization. Because glibc’s memory allocator keeps freed pages in per‑thread arenas, the OS memory usage remained high, giving the impression of a leak. Restricting the scan path or upgrading Spring Boot resolves the problem.

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.

DebuggingJavaperformancememory leakSpring BootNative Memory
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.