Diagnosing Excessive Off‑Heap Memory Usage in a Spring Boot Application
The article details a step‑by‑step investigation of why a Spring Boot service migrated to the MDP framework consumed far more physical memory than its 4 GB heap, revealing native‑code allocations, memory‑pool behavior of glibc and tcmalloc, and how limiting MCC scan paths or upgrading Spring Boot resolves the off‑heap leak.
After moving a project to the MDP framework (based on Spring Boot) the service began reporting high Swap usage; although the JVM was configured with a 4 GB heap, the process’s resident memory reached 7 GB. Initial JVM flags were -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+AlwaysPreTouch -XX:ReservedCodeCacheSize=128m -XX:InitialCodeCacheSize=128m -Xss512k -Xmx4g -Xms4g -XX:+UseG1GC -XX:G1HeapRegionSize=4M .
Using jcmd pid VM.native_memory detail the author observed that the committed memory reported by the JVM was far lower than the physical usage, indicating large native allocations outside the heap. pmap and strace showed many 64 MB mmap regions that were not accounted for by JVM tools.
The investigation proceeded with several system‑level tools:
Added -XX:NativeMemoryTracking=detail and inspected the native memory breakdown.
Ran strace -f -e brk,mmap,munmap -p pid to trace memory‑related syscalls, confirming numerous mmap calls.
Used GDB to dump suspicious memory regions and jstack to locate the threads performing the allocations.
These traces pointed to the Meituan Configuration Center (MCC) which uses Reflections to scan all JARs. During the scan Spring Boot’s ZipInflaterInputStream inflates JAR entries with Inflater , which allocates off‑heap memory via native code. The Inflater relies on a finalize method to free this memory, so the release depends on GC.
Further analysis revealed that the underlying glibc memory allocator (and tcmalloc used by gperftools) maintains per‑thread 64 MB arenas that are not returned to the OS after free, causing the appearance of a leak. A custom malloc implementation using mmap demonstrated similar behavior, confirming the allocator’s role.
To solve the problem the author limited MCC’s scan to specific JARs, eliminating the massive off‑heap allocations. Additionally, newer Spring Boot versions (2.0.5.RELEASE) have fixed the issue by explicitly releasing the Inflater’s native buffers, so upgrading also resolves the leak.
The article concludes that the observed memory growth was not a true leak but a combination of aggressive package scanning, reliance on finalizers, and allocator arena retention; proper configuration and library updates prevent the issue.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.