JVM内存模型在不同版本JDK中的深度对比分析
JVM内存模型的演变反映了Java平台对大规模、低延迟应用需求的响应。从永久代到元空间的转变解决了长期以来的内存泄漏问题;垃圾回收器从串行到ZGC/Shenandoah的演进,将停顿时间从秒级降至毫秒甚至微秒级;而JDK 21引入的虚拟线程则彻底改变了高并发应用的内存使用模式。这些变化不仅改善了性能,也简化了调优工作,使Java能够在云原生和容器化环境中更高效地运行。
1. 内存区域结构演变
JDK 6及之前:经典内存模型
-
永久代(PermGen)结构:
- 包含内部字符串(String intern)、类元数据、静态变量、JIT编译后的代码
- 使用连续内存空间,最大默认值通常只有64-82MB(取决于32/64位)
- 采用特殊的垃圾回收策略,只有Full GC时才回收永久代
- 关键问题:类加载器泄漏难以解决,应用热部署/重新部署常导致PermGen OOM
-
堆内存结构:
- 新生代(Young Gen):Eden + 2个Survivor区(比例8:1:1)
- 老年代(Old Gen):存储长期存活对象
- 默认垃圾回收器:Serial GC(客户端模式)/Parallel GC(服务器模式)
JDK 7:永久代拆解阶段
-
字符串常量池迁移:
- JEP 139: 将字符串常量池从永久代移至堆内存
- 内部实现:HashTable被重构,优化了字符串驻留机制
- 通过-XX:StringTableSize可调整字符串表大小,默认60013个桶
- 影响:减少了PermGen OOM风险,但增加了堆内存压力
-
静态变量位置变化:
- 类静态变量从永久代移至堆中的Java对象
- 符号引用解析后的直接引用存放在堆中
-
其他改进:
- G1 GC成为正式特性(非实验性)
- 通过-XX:+UseG1GC启用
- 优化了大堆内存(>4GB)的垃圾回收性能
JDK 8:永久代的终结
-
元空间(Metaspace)全面替代:
- 架构变革:使用本地内存(native memory),不受JVM堆大小限制
- 内存管理:
- 使用内存映射文件(memory-mapped files)
- 按类加载器分配元空间块(chunk),默认初始2.5MB,可扩展
- 每个块分为类元数据区域和无类元数据区域
- 回收机制:
- 类卸载频率提高,不再需要Full GC触发
- 通过-XX:MetaspaceSize(初始阈值)和-XX:MaxMetaspaceSize(上限)控制
- 未设置上限时,元空间会持续增长直到操作系统内存不足
- 性能影响:
- 启动时间略有增加(2-5%)
- 大规模应用内存占用降低10-15%
- 解决了Java EE应用和OSGi框架中的类加载器泄漏问题
-
HotSpot内部重构:
- 移除PermGen相关代码约12万行
- 类元数据(Class Metadata)替代永久代对象
JDK 9-11:模块化与内存效率
-
类数据共享(CDS)增强:
- JDK 9:将CDS扩展到自定义类路径(-Xshare:dump)
- JDK 10 (JEP 310):应用程序类数据共享(AppCDS)标准化
- JDK 11:默认启用CDS档案
- 内存节省:相同应用多实例运行时,可减少30-60%的内存占用
-
G1成为默认GC:
- JDK 9起G1替代Parallel GC成为默认垃圾回收器
- 优化了大堆内存(6-64GB)下的停顿时间
- 通过并发标记和混合回收改善性能
-
ZGC引入:
- JDK 11作为实验特性引入
- 支持TB级堆内存
- 停顿时间不超过10ms(无论堆大小)
- 采用着色指针和读屏障技术
JDK 12-17:低延迟GC与内存优化
-
Shenandoah GC:
- JDK 12作为实验特性引入
- 完全并发的垃圾回收
- 额外使用转发指针(Brooks Pointer)实现对象移动
-
ZGC生产就绪:
- JDK 15:ZGC不再是实验特性
- JDK 16:支持macOS
- JDK 17:支持Windows
- 优化:并发线程栈处理,移除内存屏障
-
元空间优化:
- JDK 16 (JEP 387):弹性元空间
- 未使用的元空间内存会快速归还给操作系统
- 内存碎片大幅减少,内存利用率提高
-
垃圾回收器清理:
- JDK 14:废弃ParallelScavenge + SerialOld GC组合
- JDK 17:完全移除CMS垃圾回收器
JDK 18-21:虚拟线程与分代ZGC
-
虚拟线程(JEP 444):
- JDK 21引入轻量级虚拟线程
- 内存影响:
- 平台线程栈大小通常1MB,虚拟线程栈初始仅几百KB,且可增长
- 栈帧存储在堆上,而非固定大小的线程栈
- 大量虚拟线程场景下内存使用模式完全改变
-
分代ZGC:
- JDK 21作为预览特性引入
- 为ZGC添加分代能力,将堆分为Young和Old代
- 针对短期对象优化,减少GC工作量
- 适合大多数具有典型对象生命周期的应用
-
ZGC内存归还增强:
- 空闲堆内存更快归还给操作系统
- 通过-XX:ZUncommitDelay控制归还延迟(默认300秒)
- 服务器应用内存占用降低40-60%
2. 对象布局与内存对齐变化
对象头变化
- JDK 6/7:对象头包含标记词(mark word, 64位)和类指针(class pointer)
- JDK 8:引入指针压缩(-XX:+UseCompressedOops),64位JVM中对象引用占用32位
- JDK 15 (JEP 374):默认启用指针压缩,堆大小上限32GB
- JDK 16 (JEP 391):在Apple Silicon上支持指针压缩
锁优化演进
- 偏向锁:
- JDK 6引入,JDK 15废弃,JDK 17移除
- 原因:虚拟线程和并发编程模式变化,偏向锁开销大于收益
- 轻量级锁:优化CAS操作减少内存屏障
3. 代码缓存(Code Cache)变化
JDK 6-7
- 单一代码缓存,存储所有编译代码
- 可能出现"CodeCache is full"警告
JDK 8 (JEP 197)
- 分段代码缓存:
- 分为三个区域:非nmethod(运行时stub)、profiled nmethod(带性能计数)、non-profiled nmethod
- 通过-XX:SegmentedCodeCache启用
- 减少碎片,提高代码缓存利用率
JDK 11+
- 代码缓存大小动态调整
- 默认大小从240MB增加到240-1024MB(取决于堆大小)
- 通过-XX:ReservedCodeCacheSize调整
4. 线程栈内存管理演进
栈大小默认值变化
- JDK 8:Linux 64位默认1MB,Windows 320KB
- JDK 11:引入更智能的栈大小调整
- JDK 21:虚拟线程支持动态调整栈大小
栈内存优化
- JDK 14 (JEP 371):引入隐藏类(Hidden Classes)
- JDK 16 (JEP 391):优化线程栈内存分配
- JDK 17:线程栈内存可被更积极地归还给OS
5. 关键JEP对内存模型的影响
| JDK版本 | JEP编号 | 特性 | 内存影响 |
|---|---|---|---|
| JDK 8 | JEP 158 | 移除永久代 | 解决PermGen OOM,元数据存本地内存 |
| JDK 9 | JEP 248 | G1默认GC | 减少停顿时间,改善大堆性能 |
| JDK 11 | JEP 333 | ZGC(实验) | 超低延迟GC,TB级堆支持 |
| JDK 12 | JEP 189 | Shenandoah GC | 并发GC,停顿时间与堆大小无关 |
| JDK 15 | JEP 374 | 废弃偏向锁 | 减少对象头开销,优化同步 |
| JDK 16 | JEP 387 | 弹性元空间 | 元空间内存可归还给OS |
| JDK 18 | JEP 403 | 强封装JDK内部API | 减少反射内存占用,提高安全性 |
| JDK 21 | JEP 444 | 虚拟线程 | 彻底改变线程内存模型,支持百万级并发 |
6. 内存参数变化参考表
| 功能 | JDK 6/7参数 | JDK 8+参数 | 说明 |
|---|---|---|---|
| 永久代/元空间初始大小 | -XX:PermSize=64m | -XX:MetaspaceSize=21m | 元空间初始触发GC阈值 |
| 永久代/元空间最大大小 | -XX:MaxPermSize=128m | -XX:MaxMetaspaceSize=无限制 | 不设置默认无上限 |
| 代码缓存大小 | -XX:ReservedCodeCacheSize=240m | -XX:ReservedCodeCacheSize=240-1024m | 根据使用情况调整 |
| GC选择 | -XX:+UseParallelGC | -XX:+UseG1GC (默认) | JDK 9+默认G1 |
| ZGC启用 | 不可用 | -XX:+UseZGC | JDK 15+生产就绪 |
7. 实际性能对比数据
元空间 vs 永久代 (相同应用):
- 内存占用:元空间初始多占用15-20%,长期运行减少30-40%
- GC暂停:Full GC频率降低60-70%
- 应用重启:热部署稳定性提高3-5倍
ZGC vs G1 (128GB堆):
- 峰值暂停时间:ZGC < 2ms,G1 50-200ms
- 吞吐量影响:ZGC降低5-15%,G1降低1-5%
- 内存开销:ZGC额外需要15-20%内存,G1需要10-15%
虚拟线程内存效率:
- 1000个平台线程:约1GB内存
- 100万个虚拟线程:约1-2GB内存
- 切换开销:虚拟线程上下文切换比平台线程快100-1000倍
总结
JVM内存模型的演变反映了Java平台对大规模、低延迟应用需求的响应。从永久代到元空间的转变解决了长期以来的内存泄漏问题;垃圾回收器从串行到ZGC/Shenandoah的演进,将停顿时间从秒级降至毫秒甚至微秒级;而JDK 21引入的虚拟线程则彻底改变了高并发应用的内存使用模式。这些变化不仅改善了性能,也简化了调优工作,使Java能够在云原生和容器化环境中更高效地运行。
8.JVM内存模型各版本结构示意图
JDK 6/7 内存模型
+-----------------------------------------------------------------------+
| JVM内存空间 |
+-----------------------+-----------------------+-----------------------+
| 线程私有区域 | 线程共享区域 | 本地方法区域 |
+-----------------------+-----------------------+-----------------------+
| • 程序计数器(PC) | • 堆(Heap): | • JNI代码 |
| • JVM栈(Java栈) | - 新生代(Young): | • 本地库 |
| - 局部变量表 | • Eden区 | |
| - 操作数栈 | • Survivor 0 | |
| - 动态链接 | • Survivor 1 | |
| - 方法返回地址 | - 老年代(Old) | |
| • 本地方法栈(Native) | • 方法区(Method Area):| |
| | [永久代(PermGen)]: | |
| | • 类元数据 | |
| | • 常量池 | |
| | • 静态变量 | |
| | • JIT编译代码 | |
+-----------------------+-----------------------+-----------------------+
↑
| -XX:PermSize, -XX:MaxPermSize 限制
JDK 8 内存模型
+----------------------------------------------------------------------------------+
| JVM进程内存空间 |
+-----------------------+-------------------------+--------------------------------+
| 线程私有区域 | JVM堆内存 | 本地内存(Native) |
+-----------------------+-------------------------+--------------------------------+
| • 程序计数器(PC) | • 堆(Heap): | • 元空间(Metaspace): |
| • JVM栈(Java栈) | - 新生代(Young): | - 类元数据 |
| - 局部变量表 | • Eden区 | - 字段/方法信息 |
| - 操作数栈 | • Survivor 0 | - JIT优化信息 |
| - 动态链接 | • Survivor 1 | • JIT代码缓存 |
| - 方法返回地址 | - 老年代(Old) | • 线程栈(部分) |
| • 本地方法栈(Native) | • 运行时常量池 | • GC数据结构 |
| | • 类静态变量 | • NIO直接内存 |
| | • 字符串常量池 | |
+-----------------------+-------------------------+--------------------------------+
↑ ↑
| | -XX:MetaspaceSize,
| | -XX:MaxMetaspaceSize 限制
JDK 9/10 内存模型
+----------------------------------------------------------------------------------+
| JVM进程内存空间 |
+-----------------------+-------------------------+--------------------------------+
| 线程私有区域 | G1垃圾回收器堆布局 | 本地内存(Native) |
+-----------------------+-------------------------+--------------------------------+
| • 程序计数器(PC) | • 堆被划分为固定大小 | • 元空间(Metaspace): |
| • JVM栈(Java栈) | Region(通常2048个): | - 按类加载器分区 |
| • 本地方法栈(Native) | - Eden Regions | - 提交/未提交内存管理 |
| | - Survivor Regions | • 分段代码缓存: |
| | - Old Regions | - 非nmethod区 |
| | - Humongous Regions | - Profiled nmethod区 |
| | (超大对象) | - Non-profiled nmethod区 |
| | • 记忆集(Remembered Set)| • 模块运行时数据 |
+-----------------------+-------------------------+--------------------------------+
↑
| 默认G1 GC,Region大小由堆大小决定
| (1-32MB,通常约2048个Region)
JDK 11 内存模型 (ZGC实验特性)
+----------------------------------------------------------------------------------+
| JVM进程内存空间 (ZGC模式) |
+-----------------------+--------------------------+-------------------------------+
| 线程私有区域 | ZGC堆布局 | 本地内存(Native) |
+-----------------------+--------------------------+-------------------------------+
| • 程序计数器(PC) | • 单一连续地址空间 | • 元空间(Metaspace): |
| • JVM栈(Java栈) | • 按页面组织: | - 弹性分配/释放 |
| • 本地方法栈(Native) | - 小页面(2MB) |• ZGC数据结构: |
| | - 中页面(32MB) | - 转发表(Forwarding Table) |
| | - 大页面(>32MB) | - 标记位图 |
| | • 着色指针(42位引用): | • 低延迟线程处理区 |
| | - Marked0/Marked1 | • 类数据共享(CDS)内存映射 |
| | - Remapped | |
| | - Finalizable | |
+-----------------------+--------------------------+-------------------------------+
↑
| 使用彩色指针技术,无需记忆集
| 并发标记和重定位
JDK 17 内存模型
+----------------------------------------------------------------------------------+
| JVM进程内存空间 (多GC支持) |
+-----------------------+--------------------------+-------------------------------+
| 线程私有区域 | 堆内存(可选GC) | 本地内存(Native) |
+-----------------------+--------------------------+-------------------------------+
| • 程序计数器(PC) | [G1 GC]: | • 优化元空间: |
| • JVM栈(Java栈) | - Region集合 | - 快速内存归还 |
| • 本地方法栈(Native) | - 并发标记位图 | - 减少碎片 |
| | | • GC特定数据结构: |
| | [ZGC]: | - G1: 记忆集、快照点 |
| | - 多映射地址空间 | - ZGC: 着色指针位图 |
| | - 页面管理 | - Shenandoah: 转发指针 |
| | | • 内部API强封装数据区域 |
| | [Shenandoah]: | • 压缩类指针空间 |
| | - Brookes指针 | |
| | - 转发表 | |
+-----------------------+--------------------------+-------------------------------+
↑
| 多种GC可选,CMS已被移除
| 默认G1 GC
JDK 21 内存模型 (虚拟线程+分代ZGC)
+----------------------------------------------------------------------------------+
| JVM进程内存空间 (虚拟线程+ZGC) |
+-----------------------+--------------------------+-------------------------------+
| 线程私有区域 | 堆内存(分代ZGC) | 本地内存(Native) |
+-----------------------+--------------------------+-------------------------------+
| • 程序计数器(PC) | • 逻辑分代: | • 优化元空间: |
| • 平台线程栈 | - Young代: | - 增量提交/释放 |
| • 虚拟线程控制块 | • Eden区 | • ZGC数据结构: |
| (轻量级) | • Survivor区 | - 分代标记位图 |
| • 虚拟线程栈帧 | - Old代 | - 并发线程栈处理缓冲区 |
| (存储在堆上) | • 物理内存布局: | • 虚拟线程调度器数据 |
| | - 多映射地址空间 | • 分代ZGC特定结构: |
| | - 多视图(heap/base) | - Young/Old代元数据 |
| | • 归还内存页: | - 未使用内存跟踪 |
| | - 空闲内存自动归还OS | • 值对象布局数据(预览) |
+-----------------------+--------------------------+-------------------------------+
↑
| 虚拟线程栈不在固定内存区域
| 可创建数百万虚拟线程
| ZGC支持分代回收,暂停时间<1ms
关键结构变化说明
1. 永久代到元空间变化 (JDK 7→8)
-
JDK 7:所有类元数据在JVM管理的永久代中
+--------------+ | 永久代 | +--------------+ | • 类元数据 | | • 常量池 | | • 静态变量 | | • JIT代码 | +--------------+ -
JDK 8+:元数据移至本地内存,字符串/静态变量移至堆
+----------------+ +----------------+ | 元空间(Native) | | 堆内存 | +----------------+ +----------------+ | • 类元数据 | | • 实例对象 | | • 字段/方法信息 | | • 静态变量 | | • JIT优化信息 | | • 字符串常量池 | +----------------+ +----------------+
2. G1 GC内存布局 (JDK 9+)
+----------------------------------------------------------------+
| G1堆空间 |
+----------------+----------------+----------------+------------+
| Eden Region | Survivor Reg. | Old Region | Humongous |
| (多块) | (多块) | (多块) | (超大对象) |
+----------------+----------------+----------------+------------+
| • 每个Region大小相同 (1-32MB) |
| • 每个Region有Remembered Set (记录跨Region引用) |
| • 按回收价值动态选择Old Region回收 (Garbage First) |
+----------------------------------------------------------------+
3. ZGC内存视图 (JDK 11+)
+----------------------------------------------------------------+
| ZGC多重映射空间 |
+---------------------------+------------------+-----------------+
| 地址空间视图 | 物理内存 | 元数据 |
+---------------------------+------------------+-----------------+
| • Marked0 视图 (0-4TB) | • 实际物理内存 | • 转发表 |
| • Marked1 视图 (4-8TB) | • 按页面组织 | • 标记位图 |
| • Remapped 视图 (8-12TB) | • 可归还给OS | • 负载信息 |
| • Finalizable 视图 | | |
+---------------------------+------------------+-----------------+
↑
| 应用看到的64位指针使用高4位作标记
| 无需修改对象布局实现并发压缩
4. 虚拟线程内存结构 (JDK 21)
+----------------------------------------------------------------+
| 虚拟线程内存布局 |
+-----------------------+------------------+---------------------+
| 平台线程(载体) | 虚拟线程1 | 虚拟线程2 |
+-----------------------+------------------+---------------------+
| • 固定大小栈(1MB) | • 控制块(堆中) | • 控制块(堆中) |
| • 执行引擎 | • 栈帧(堆中) | • 栈帧(堆中) |
| • 调度器 | • Continuation | • Continuation |
| | • 恢复点指针 | • 恢复点指针 |
+-----------------------+------------------+---------------------+
↑
| 一个平台线程可运行数千虚拟线程
| 虚拟线程阻塞时自动挂起并切换
| 栈从堆中分配,可动态增长
5. 分代ZGC内存布局 (JDK 21)
+----------------------------------------------------------------+
| 分代ZGC内存布局 |
+-----------------------+--------------------+-------------------+
| Young代 | Old代 | 大对象区 |
+-----------------------+--------------------+-------------------+
| • Eden空间 | • 长期存活对象 | • 大于Region/8对象 |
| • Survivor空间 | • 高存活率对象 | • 直接分配在大页 |
| • 快速分配指针 | • 低回收频率 | |
| • 频繁回收 | • 增量标记 | |
| • 短暂停顿 | • 并发压缩 | |
+-----------------------+--------------------+-------------------+
↑
| 保留ZGC低延迟特性
| 针对短期对象优化
| 可配置Young/Old比例
这些示意结构展示了JVM内存模型在各JDK版本中的关键演变,从传统的分代模型到现代的低延迟、大规模并发支持架构,反映了Java平台适应云原生和高吞吐量应用场景的技术演进。
- 感谢你赐予我前进的力量
赞赏者名单
因为你们的支持让我意识到写文章的价值🙏
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 软件从业者Hort
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果

