Java内存使用量远超Native Memory Tracking报告值排查求助
排查Spring Boot应用内存占用远超NMT报告的问题
我在优化Spring Boot应用内存占用时,发现实际内存使用量远超预期。使用Java的Native Memory Tracking(NMT)功能检测后,报告显示已提交内存为601040KB(约600MB):
> jcmd 1 VM.native_memory 1: Native Memory Tracking: (Omitting categories weighting less than 1KB) Total: reserved=18460740KB, committed=601040KB malloc: 97616KB #503895 mmap: reserved=18363124KB, committed=503424KB - Java Heap (reserved=16465920KB, committed=262144KB) (mmap: reserved=16465920KB, committed=262144KB) [...]
但top、ps等系统工具报告的内存使用量要高得多(约1GB驻留集大小/约1.3GB内存大小):
> ps -e -o pid,pmem,rss,size,cmd PID %MEM RSS SIZE CMD 1 1.6 1061716 1311316 java org.springframework.boot.loader.JarLauncher [...]
我在NMT文档中找到的线索:
由于NMT不跟踪非JVM代码的内存分配,您可能需要使用操作系统支持的工具来检测原生代码中的内存泄漏。
但我不确定哪些非JVM代码会占用数百MB内存,有没有办法找出内存占用来源?
额外信息
VM.flags输出
> jcmd 1 VM.flags 1: -XX:CICompilerCount=12 -XX:ConcGCThreads=3 -XX:G1ConcRefinementThreads=13 -XX:G1EagerReclaimRemSetThreshold=64 -XX:G1HeapRegionSize=8388608 -XX:G1PeriodicGCInterval=60000 -XX:G1RemSetArrayOfCardsEntries=64 -XX:G1RemSetHowlMaxNumBuckets=8 -XX:G1RemSetHowlNumBuckets=8 -XX:GCDrainStackTargetSize=64 -XX:InitialHeapSize=1056964608 -XX:MarkStackSize=4194304 -XX:MaxHeapFreeRatio=20 -XX:MaxHeapSize=16861102080 -XX:MaxNewSize=10116661248 -XX:MinHeapDeltaBytes=8388608 -XX:MinHeapFreeRatio=5 -XX:MinHeapSize=268435456 -XX:NativeMemoryTracking=detail -XX:NonNMethodCodeHeapSize=7602480 -XX:NonProfiledCodeHeapSize=122027880 -XX:+PrintGC -XX:ProfiledCodeHeapSize=122027880 -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:SoftMaxHeapSize=268435456 -XX:-THPStackMitigation -XX:+UseCompressedOops -XX:+UseG1GC
VM.system_properties输出
> jcmd 1 VM.system_properties 1: #Thu Feb 08 11:52:42 CET 2024 CONSOLE_LOG_CHARSET=UTF-8 FILE_LOG_CHARSET=UTF-8 PID=1 catalina.base=/tmp/tomcat.8080.1256868023389071013 catalina.home=/tmp/tomcat.8080.1256868023389071013 catalina.useNaming=false file.encoding=UTF-8 file.separator=/ java.awt.headless=true java.class.path=. java.class.version=65.0 java.home=/opt/java/openjdk java.io.tmpdir=/tmp java.library.path=/usr/java/packages/lib\:/usr/lib64\:/lib64\:/lib\:/usr/lib java.runtime.name=OpenJDK Runtime Environment java.runtime.version=21.0.1+12-LTS java.specification.name=Java Platform API Specification java.specification.vendor=Oracle Corporation java.specification.version=21 java.vendor=Eclipse Adoptium java.vendor.url=https\://adoptium.net/ java.vendor.url.bug=https\://github.com/adoptium/adoptium-support/issues java.vendor.version=Temurin-21.0.1+12 java.version=21.0.1 java.version.date=2023-10-17 java.vm.compressedOopsMode=Zero based java.vm.info=mixed mode, sharing java.vm.name=OpenJDK 64-Bit Server VM java.vm.specification.name=Java Virtual Machine Specification java.vm.specification.vendor=Oracle Corporation java.vm.specification.version=21 java.vm.vendor=Eclipse Adoptium java.vm.version=21.0.1+12-LTS jdk.debug=release line.separator=\n native.encoding=UTF-8 os.arch=amd64 os.name=Linux os.version=5.18.13-200.fc36.x86_64 path.separator=\: spring.beaninfo.ignore=true stderr.encoding=UTF-8 stdout.encoding=UTF-8 sun.arch.data.model=64 sun.boot.library.path=/opt/java/openjdk/lib sun.cpu.endian=little sun.io.unicode.encoding=UnicodeLittle sun.java.command=org.springframework.boot.loader.JarLauncher sun.java.launcher=SUN_STANDARD sun.jnu.encoding=UTF-8 sun.management.compiler=HotSpot 64-Bit Tiered Compilers user.country=US user.dir=/application user.home=/root user.language=en user.name=root user.timezone=Europe/Berlin
排查非JVM内存占用的实用方法
- 用
pmap分析内存映射:执行pmap -x <PID>,查看进程的内存映射详情,重点关注anon类型的大内存区域,以及加载的共享库(.so文件)占用的内存。如果某个共享库或匿名映射占用异常大的内存,大概率是问题来源。 - 通过
perf跟踪内存分配:运行perf record -g -p <PID>记录进程的内存分配调用栈,再用perf report分析,找出频繁分配内存的原生函数,定位到对应的库或代码。 - 检查JNI调用和第三方库:Spring Boot应用依赖的第三方库(如数据库驱动、序列化库、原生扩展)常通过JNI分配内存,排查这些库的使用情况,尤其是是否存在内存泄漏。
- 区分共享内存与独占内存:系统工具报告的RSS包含进程共享的内存页(如共享库、文件缓存),用
smem -t -k -p查看进程的实际独占内存(USS),明确哪些是进程自身占用的内存。 - 对比NMT的summary和detail输出:执行
jcmd <PID> VM.native_memory summary和detail,确认NMT已跟踪的所有JVM内存类别,排除JVM内部未被正确统计的部分。
内容的提问来源于stack exchange,提问作者Nikos Epping
相关产品推荐
相关产品推荐

