JVM内存模型
一、五大核心区域
1. 线程私有区域
(1) 程序计数器
- 线程私有
- 唯一无 OOM区域
- 作用:记录当前线程执行字节码行号,支撑分支跳转、循环、异常恢复,多线程切换靠它恢复执行位置
(2) 虚拟机栈(Java 栈)
- 线程私有
- 作用:每个线程都会创建一个栈帧,存放局部变量表(包括引用变量等)、操作数栈、动态链接、方法返回地址
- 方法嵌套层级太深、递归死循环,报栈深度超限
StackOverflowError - 栈空间不足报
OutOfMemoryError(OOM)异常
(3) 本地方法栈
- 线程私有
- 作用:专门服务 Native 方法,同样会栈溢出 / OOM
-
HotSpot中甚至把它和虚拟机栈合二为一
2. 线程共享区域
(1) 堆 Heap(JVM最大的一块内存)
- 线程共享
- 作用:存放所有的对象实例、 数组实例
- 垃圾回收主要操作的区域,也是唯一频繁 GC 区域,OOM 最常见:
Java heap space - JDK7堆内存结构:新生代(Young)+ 老年代(Old)+ 永久代(PermGen,方法区实现)
- 永久代(PermGen,堆内)存放:类元数据、方法字节码、运行时常量池(含字符串常量池)、静态变量、JVM 内部表等
- JDK8堆内存结构:新生代(Young)+ 老年代(Old);PermGen 彻底移除,改用元空间(Metaspace,堆外)
PS:为什么 JDK8 要移除 永久代PermGen?
- OOM 频发:PermGen 大小难预估,加载大量类 / 动态生成类(如 CGLib、Spring)易溢出。
- GC 效率低:PermGen 回收条件苛刻(类卸载、FullGC),回收效果差。
- 融合 JRockit:Oracle 收购后整合 HotSpot 与 JRockit,JRockit 无 PermGen 设计,统一为 Metaspace。
- 本地内存更灵活:Metaspace 使用系统内存,可动态扩展,减少 JVM 堆内存压力。
(2) 方法区(元空间 Metaspace)
- JDK7叫永久代;JDK8叫元空间,直接用操作系统本地内存
- 作用:存放方法代码、类结构、常量池、方法元数据、静态变量、运行时常量池
- 元空间不足会触发 OOM 异常
二、 对象创建流程
HotSpot中对象的创建分为5步:
1、类加载检查
JVM遇到new指令时,先去常量池检查该类是否已经被加载、解析、初始化过,如果没有,先执行类加载流程。
PS:这里为什么是去常量池中检查?
- 常量池存放类、方法、字段、接口、符号引用所有依赖信息,是 Class 的索引字典。
- 类加载验证、解析阶段都依赖常量池:校验 Class 合法性、继承权限、字节码规范。
- 编译时是符号引用存在常量池,加载时要把符号引用解析为内存直接引用,必须遍历常量池。
- 所有跨类依赖、动态链接、安全校验,全部依托常量池完成,所以类加载必须重点检查常量池。
PS:类加载三大阶段:验证、准备、解析为什么都依赖常量池?
(1) 验证阶段 → 靠常量池做合法性校验
虚拟机要检查这个 Class 是不是合规、安全、没被篡改,全部看常量池:
- 检查魔术号、版本号(在常量池相关结构),防止非法 Class
- 检查继承关系:父类是否 final、是否合法继承
- 检查访问权限:private/protected/public 跨类访问合规性
- 检查方法签名、字段签名是否合法,有没有非法重载、重写
没有常量池,JVM 根本不知道这个类依赖谁、结构对不对。
(2) 准备阶段 → 给常量池里的静态变量分配默认值
静态字段的引用信息都在常量池,JVM 根据常量池记录的字段类型,分配内存、赋默认零值。
(3) 解析阶段 → 核心:符号引用 → 直接引用
这是最关键原因:
- 编译时:只生成符号引用(字符串形式,比如
Student.say())存在常量池 - 加载解析时:把常量池里的符号引用,替换成内存直接地址(指针 / 偏移量)
不遍历常量池,就没法完成引用绑定,代码根本跑不起来。
2、分配内存(指针碰撞 / 空闲列表)
类加载完成后,计算所需内存的大小,在堆中划分空间。
分配方式:内存规整使用指针碰撞,内存不规整用空闲列表
多线程环境使用 CAS + 失败重试保证线程安全
(1) 指针碰撞
原理:
- 堆里维护一个分界指针:指针左边已分配,右边空闲。
- 新对象要分配内存:直接把指针向右挪动对象大小即可。
- 分配极快,只做一次指针偏移,无遍历、无查询。
适用场景:内存规整、没有内存碎片典型:Serial、ParNew 带复制算法的新生代
优点:分配速度极致快,无额外开销;实现简单
缺点:只能用在内存绝对规整的区域;一旦产生内存碎片,就不能用
(2) 空闲列表
原理:
- JVM 维护一个空闲列表,记录每一块空闲内存的起始地址 + 大小。
- 新对象分配:遍历列表,找一块能装下对象的空闲块。
- 分配后更新列表(拆分 / 移除空闲块)。
适用场景:内存不规整、有大量内存碎片典型:老年代、标记清除算法之后。
优点:不在乎内存是否规整,碎片化也能正常分配;适配标记 - 清除这种会产生碎片的 GC
缺点:需要遍历列表找合适空闲块,分配速度慢;维护列表有开销、容易产生内部碎片
3、初始化零值
内存分配完成后,JVM把所有字段赋默认值,比如int=0、boolean=false、引用=null,保证对象不赋值就能直接使用。
4、设置对象头
给对象设置头信息,包括哈希码、GC分代年龄、锁状态、类型指针(指向类元数据)
PS:为什么要设置对象头信息?
- 对象头里保存了GC 分代年龄、锁状态、哈希码、类型指针;
- JVM 依靠对象头完成垃圾回收、锁机制、类型判断、多态和反射,是 JVM 管理 Java 对象的基础元数据,缺一不可。
5、执行<init>构造方法
执行init构造方法,完成业务层面的初始化,整个对象创建完成。
三、 对象内存布局
1、对象头
对象头包含两部分,MarkWord和类型指针
(1) MarkWord
存放锁状态、GC 分代年龄、哈希码、线程持有锁,数据长度随虚拟机位数变化。
(2) 类型指针
类型指针指向方法区的类元数据,确定对象属于哪个类。
PS:类元数据是什么?
类元数据是描述一个类结构的所有信息。
- 类加载要靠它:校验类合法性、解析符号引用
- 多态、动态绑定靠它:找虚方法、执行重写逻辑
- instanceof 类型判断靠它
- 反射靠它:运行时拿字段、拿方法、创建对象
- GC 靠它:识别对象类型、对象内存布局
- 动态代理、Spring AOP、CGLib 都要读取、生成类元数据
2、实例数据
实例数据存放对象真正的成员变量信息,包括继承自父类的变量和自身定义变量,分配时有固定顺序规则。
PS:为什么分配时有固定顺序规则?(省内存 + 快访问 + 布局稳定)
- 实例数据按大小从大到小、父类优先子类排布,减少内存对齐填充、节省堆内存;
- 满足 CPU 硬件对齐,提升访问性能;
- 保证继承体系字段偏移固定,支持反射、Unsafe、序列化等底层依赖稳定的内存布局。
3、对齐填充
对齐填充不是必须存在的,HotSpot要求对象内存大小必须是8字节的整数倍,不够的话用填充字节补齐,单纯为了内存对齐。
4、指针压缩
(1) 为什么要有指针压缩
64 位 JVM:
- 原生类型指针、对象引用 都是 8 字节
- 对象头变大、整体对象占用变大、浪费内存、CPU 缓存效率低
(2) 核心原理
把 64 位地址指针 → 压缩成 32 位(4 字节)JVM 底层做地址偏移换算,用 4 字节就能寻址整个堆。
(3) 生效条件
- 64 位 JVM
- 堆内存小于 32GB超过 32G,4 字节寻址不够用,自动关闭指针压缩
- JDK6 及以后 默认开启
(4) 影响对象哪里
- 对象头 Klass 类型指针
- 开启压缩:4 字节
- 关闭压缩:8 字节
- 栈里引用变量、堆内对象引用 都从 8 字节变 4 字节
- 整个对象占用变小,节省堆内存、提升 GC 效率
(5) 开启/关闭对象头变化
- 开启指针压缩:对象头 = MarkWord (8B) + Klass 指针 (4B) = 12 字节,再做 8 字节对齐
- 关闭指针压缩:对象头 = MarkWord (8B) + Klass 指针 (8B) = 16 字节
四、 对象访问方式
1、句柄访问(Handle方式)
(1) 内存结构
- 栈帧里的 引用地址 → 指向句柄池中的句柄
- 句柄包含两个指针:
- 一个指向 堆中的实例对象
- 一个指向 方法区 / 元空间的类元数据
(2) 特点
- 稳定:对象如果被 GC 移动(标记整理、复制 GC),只改句柄里的对象地址,栈里的引用不需要改。
- 多了一层中间跳转,访问速度稍慢。
- 需要单独维护句柄池,占用额外内存。
(3) 优缺点
优点是对象移动时引用无需修改、稳定性强;缺点是多一次寻址、速度慢。
(4) 适用
老式虚拟机、部分语言虚拟机,HotSpot 默认不用。
2、直接指针(HotSpot 默认,速度快)
(1) 内存结构
栈中 引用 直接指向堆里的对象起始地址。对象内部有 Klass 指针,再指向类元数据。
(2) 特点
- 少一次寻址,访问速度更快,HotSpot 虚拟机默认采用。
- 对象被 GC 移动时,所有栈上的引用都要更新地址。
- 节省句柄池内存空间。
(3) 优缺点
优点是访问更快、节省内存;缺点是对象 GC 移动时需要更新所有引用地址。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)