Java 21对比Java 8内存占用过高问题排查求助
问题背景
将Java应用从Java 8升级至Java 21后,发现内存消耗显著增加。应用部署在基于Alpine OS的容器中,使用Eclipse Temurin Build版本。
问题复现
创建了可复现问题的小型项目,当堆参数设置为-Xms256m -Xmx512m且容器内存限制为1G时,docker stats显示Java进程内存占用从231 MiB跃升至314 MiB,增加了三分之一。

初步分析
启用Native Memory Tracking (NMT)对比Java 8和Java 21的内存占用:
Java 8 NMT输出
Native Memory Tracking: Total: reserved=1929874KB, committed=393190KB - Java Heap (reserved=524288KB, committed=262208KB) (mmap: reserved=524288KB, committed=262208KB) - Class (reserved=1099014KB, committed=57566KB) (classes #10367) (malloc=1286KB #10537) (mmap: reserved=1097728KB, committed=56280KB) - Thread (reserved=31968KB, committed=31968KB) (thread #32) (stack: reserved=31829KB, committed=31829KB) (malloc=106KB #186) (arena=32KB #58) - Code (reserved=253118KB, committed=20810KB) (malloc=3518KB #5282) (mmap: reserved=249600KB, committed=17292KB) - GC (reserved=1738KB, committed=890KB) (malloc=26KB #174) (mmap: reserved=1712KB, committed=864KB) - Compiler (reserved=159KB, committed=159KB) (malloc=24KB #469) (arena=135KB #7) - Internal (reserved=1844KB, committed=1844KB) (malloc=1812KB #12019) (mmap: reserved=32KB, committed=32KB) - Symbol (reserved=15183KB, committed=15183KB) (malloc=11915KB #122017) (arena=3268KB #1) - Native Memory Tracking (reserved=2366KB, committed=2366KB) (malloc=7KB #90) (tracking overhead=2359KB) - Arena Chunk (reserved=196KB, committed=196KB) (malloc=196KB)
Java 21 NMT输出
Native Memory Tracking: Total: reserved=2044186KB, committed=424038KB malloc: 42596KB #337605 mmap: reserved=2001590KB, committed=381442KB - Java Heap (reserved=524288KB, committed=262208KB) (mmap: reserved=524288KB, committed=262208KB) - Class (reserved=1050068KB, committed=13012KB) (classes #17813) ( instance classes #16553, array classes #1260) (malloc=1492KB #45190) (peak=1496KB #45058) (mmap: reserved=1048576KB, committed=11520KB) ( Metadata: ) ( reserved=131072KB, committed=69376KB) ( used=68992KB) ( waste=384KB =0.55%) ( Class space:) ( reserved=1048576KB, committed=11520KB) ( used=11130KB) ( waste=390KB =3.38%) - Thread (reserved=31927KB, committed=2831KB) (thread #32) (stack: reserved=31826KB, committed=2730KB) (malloc=66KB #191) (peak=79KB #207) (arena=34KB #60) (peak=2322KB #38) - Code (reserved=249888KB, committed=24908KB) (malloc=2200KB #9067) (at peak) (mmap: reserved=247688KB, committed=22708KB) - GC (reserved=1734KB, committed=882KB) (malloc=22KB #80) (peak=250KB #137) (mmap: reserved=1712KB, committed=860KB) - Compiler (reserved=238KB, committed=238KB) (malloc=74KB #835) (peak=117KB #857) (arena=164KB #4) (peak=53061KB #30) - Internal (reserved=863KB, committed=863KB) (malloc=827KB #25496) (peak=835KB #25298) (mmap: reserved=36KB, committed=36KB) - Other (reserved=24KB, committed=24KB) (malloc=24KB #3) (peak=58KB #5) - Symbol (reserved=30116KB, committed=30116KB) (malloc=28254KB #234525) (at peak) (arena=1862KB #1) (at peak) - Native Memory Tracking (reserved=5362KB, committed=5362KB) (malloc=87KB #1569) (peak=87KB #1568) (tracking overhead=5275KB) - Shared class space (reserved=16384KB, committed=11996KB, readonly=0KB) (mmap: reserved=16384KB, committed=11996KB) - Arena Chunk (reserved=2KB, committed=2KB) (malloc=2KB #116) (peak=55748KB #1365) - Module (reserved=127KB, committed=127KB) (malloc=127KB #3158) (at peak) - Safepoint (reserved=8KB, committed=8KB) (mmap: reserved=8KB, committed=8KB) - Synchronization (reserved=1751KB, committed=1751KB) (malloc=1751KB #17227) (at peak) - Serviceability (reserved=17KB, committed=17KB) (malloc=17KB #9) (peak=17KB #11) - Metaspace (reserved=131387KB, committed=69691KB) (malloc=315KB #118) (at peak) (mmap: reserved=131072KB, committed=69376KB) - String Deduplication (reserved=1KB, committed=1KB) (malloc=1KB #8) (at peak)

最初怀疑的两个原因已被排除:
- 排除Netty的Direct Buffer/ Native Memory占用:使用无Netty的spring-pet-clinic项目测试,仍存在内存上升现象
- 排除Alpine系统默认内存分配库:在Ubuntu系统测试,同样出现内存增加问题
后续诊断方向
可采取以下额外步骤进一步排查:
对齐JVM默认参数测试:Java 8到Java 21的JVM默认参数有诸多调整,比如GC算法、元空间设置、新增特性开关等。将Java 21的参数手动对齐到Java 8的默认值,逐一测试哪些参数导致内存上升:
- 切换GC算法:Java 21默认用ZGC(64位系统),可切回Parallel GC(
-XX:+UseParallelGC)观察变化 - 固定元空间大小:设置
-XX:MetaspaceSize和-XX:MaxMetaspaceSize与Java 8一致 - 关闭新增特性:比如字符串去重(
-XX:-UseStringDeduplication)、共享类空间(-XX:-UseSharedSpaces)
- 切换GC算法:Java 21默认用ZGC(64位系统),可切回Parallel GC(
深挖NMT差异项:从NMT输出看,Java 21的Symbol区、Metaspace、Synchronization等区域占用明显更高:
- 用
jmap -histo对比Java 8和Java 21的类、字符串常量数量,分析Symbol区翻倍的原因 - 执行
jcmd <pid> VM.metaspace查看元空间的详细使用,对比Class区和Metaspace的实际占用差异 - 检查Synchronization区域的内存来源,是否有更多锁对象或同步相关资源
- 用
容器内存映射分析:在容器内使用
pmap <pid>或jcmd <pid> VM.native_memory detail获取细粒度内存映射数据,对比Java 8和Java 21的匿名映射、文件分布差异,定位新增的内存占用区域更换JDK版本测试:当前使用Eclipse Temurin,可尝试OpenJDK、Azul Zulu等其他厂商的Java 21版本,排查是否是特定构建版本的问题
长期内存趋势监控:观察应用运行数小时后的内存变化,用
async-profiler或jprofiler分析内存分配、回收频率,排查是否存在隐性内存泄漏
10月2日更新
使用无依赖的原生Java简易HTTP Server测试,仍能复现内存占用上升现象(Java 21内存略高于Java 8),只是增幅不如Spring Pet Clinic项目明显。

内容的提问来源于stack exchange,提问作者Raghu

