JVM对象创建与内存分配机制深度剖析
📌 JVM对象创建与内存分配机制深度剖析
一、对象创建流程
-
类加载检查
- 执行
new
指令时,检查常量池能否定位到类的符号引用。 - 若类未加载,先触发类加载过程(加载→验证→准备→解析→初始化)。
- 对应场景:
new
关键字、对象克隆、对象序列化。
- 执行
-
分配内存
- 划分内存方法:
- 指针碰撞(Bump the Pointer)
堆内存规整时,移动指针划分连续空间(默认方式)。 - 空闲列表(Free List)
堆内存不规整时,从空闲列表查找可用内存块。
- 指针碰撞(Bump the Pointer)
- 解决并发问题:
- CAS + 重试:保证原子性。
- TLAB(线程本地分配缓冲):
为每个线程预分配私有内存区域(参数:-XX:+UseTLAB
默认开启,-XX:TLABSize
设置大小)。
- 划分内存方法:
-
初始化
- 内存空间置零(排除对象头),若启用TLAB则提前初始化。
- 保证字段不赋初值可直接使用(默认零值:
int=0
,boolean=false
等)。
-
设置对象头(Object Header)
- 存储内容:
- Mark Word:运行时数据(哈希码、GC分代年龄、锁状态等)。
- Klass Pointer:指向类元数据的指针。
- 内存布局:
区域 说明 大小(64位系统) Mark Word 哈希码、锁状态、GC年龄等 8字节 Klass Pointer 类元数据指针(压缩后) 4字节(默认开启压缩) 数组长度(可选) 仅数组对象存在 4字节 - 锁状态标记示例:
锁状态 锁标志位 其他存储内容 无锁 01
哈希码、分代年龄 偏向锁 01
线程ID、Epoch、分代年龄 轻量级锁 00
指向栈中锁记录的指针 重量级锁 10
指向互斥量的指针 GC标记 11
空
- 存储内容:
-
执行
<init>
方法- 按程序员意图初始化对象(显式赋初值、执行构造方法)。
二、对象大小与指针压缩
-
测量工具
<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.9</version> </dependency>
ClassLayout layout = ClassLayout.parseInstance(new Object()); System.out.println(layout.toPrintable());
-
指针压缩(Compressed Oops)
- 启用参数:
-XX:+UseCompressedOops
(默认开启),禁用:-XX:-UseCompressedOops
。 - 作用:
- 64位系统下用32位指针,减少内存占用(降低约1.5倍)。
- 支持最大堆内存 32GB(超过时指针压缩失效,需用64位地址)。
- 场景:
- 堆内存 < 4GB:无需压缩。
- 堆内存 ≤ 32GB:启用压缩。
- 启用参数:
三、对象内存分配策略
-
栈上分配
- 条件:
- 开启逃逸分析(
-XX:+DoEscapeAnalysis
,默认开启)。 - 开启标量替换(
-XX:+EliminateAllocations
,默认开启)。
- 开启逃逸分析(
- 优化场景:对象未逃逸出方法作用域(如方法内临时对象)。
- 条件:
-
Eden区分配
- 对象优先在Eden区分配,空间不足触发 Minor GC。
- Survivor区分配:
- 默认比例
Eden : Survivor = 8:1:1
。 - 关闭比例自适应:
-XX:-UseAdaptiveSizePolicy
。
- 默认比例
-
大对象直入老年代
- 参数:
-XX:PretenureSizeThreshold=字节大小
(仅Serial/ParNew收集器有效)。 - 目的:避免大对象复制开销。
- 参数:
-
长期存活对象晋升老年代
- 年龄计数器:每熬过1次Minor GC年龄+1。
- 晋升阈值:
- 默认
-XX:MaxTenuringThreshold=15
(CMS收集器默认为6)。
- 默认
-
动态年龄判断
- Survivor区中同年龄对象总大小 > Survivor区50%时,≥该年龄的对象晋升老年代。
- 参数:
-XX:TargetSurvivorRatio
(指定占比阈值)。
-
老年代空间分配担保
- 流程:
- Minor GC前检查老年代剩余空间是否 > 年轻代对象总大小。
- 若不足,检查
-XX:-HandlePromotionFailure
参数。 - 老年代空间 < 历次晋升平均大小,触发 Full GC。
- 流程:
四、对象内存回收
-
判断对象死亡
- 引用计数法:存在循环引用问题(JVM未采用)。
- 可达性分析法:从GC Roots(栈局部变量、静态变量等)向下扫描引用链。
-
引用类型
类型 特点 强引用 普遍对象引用( Object obj = new Object()
)软引用 SoftReference
包裹,内存不足时回收(适合缓存)弱引用 WeakReference
包裹,下次GC必回收虚引用 PhantomReference
包裹,用于对象回收跟踪(极少用) -
finalize() 自救机制
- 对象首次标记后,若重写
finalize()
且重新关联引用链,可避免回收(仅一次机会)。
- 对象首次标记后,若重写
五、类卸载条件
同时满足以下三点视为无用类:
- 堆中不存在该类的任何实例。
- 加载该类的
ClassLoader
已被回收。 - 该类对应的
java.lang.Class
对象未被引用,无法通过反射访问。
注:关键参数总结
- 指针压缩:
-XX:+UseCompressedOops
- 逃逸分析:
-XX:+DoEscapeAnalysis
- 标量替换:
-XX:+EliminateAllocations
- 年龄阈值:
-XX:MaxTenuringThreshold=15
- 大对象阈值:
-XX:PretenureSizeThreshold=1000000
此笔记完整覆盖对象生命周期(创建→分配→回收),可直接用于CSDN博文发布。**以下是根据文档内容整理的详细笔记,已优化为Markdown格式(适用于CSDN编辑器):
📌 JVM对象创建与内存分配机制深度剖析
一、对象创建流程
-
类加载检查
- 执行
new
指令时,检查常量池能否定位到类的符号引用。 - 若类未加载,先触发类加载过程(加载→验证→准备→解析→初始化)。
- 对应场景:
new
关键字、对象克隆、对象序列化。
- 执行
-
分配内存
- 划分内存方法:
- 指针碰撞(Bump the Pointer)
堆内存规整时,移动指针划分连续空间(默认方式)。 - 空闲列表(Free List)
堆内存不规整时,从空闲列表查找可用内存块。
- 指针碰撞(Bump the Pointer)
- 解决并发问题:
- CAS + 重试:保证原子性。
- TLAB(线程本地分配缓冲):
为每个线程预分配私有内存区域(参数:-XX:+UseTLAB
默认开启,-XX:TLABSize
设置大小)。
- 划分内存方法:
-
初始化
- 内存空间置零(排除对象头),若启用TLAB则提前初始化。
- 保证字段不赋初值可直接使用(默认零值:
int=0
,boolean=false
等)。
-
设置对象头(Object Header)
- 存储内容:
- Mark Word:运行时数据(哈希码、GC分代年龄、锁状态等)。
- Klass Pointer:指向类元数据的指针。
- 内存布局:
区域 说明 大小(64位系统) Mark Word 哈希码、锁状态、GC年龄等 8字节 Klass Pointer 类元数据指针(压缩后) 4字节(默认开启压缩) 数组长度(可选) 仅数组对象存在 4字节 - 锁状态标记示例:
锁状态 锁标志位 其他存储内容 无锁 01
哈希码、分代年龄 偏向锁 01
线程ID、Epoch、分代年龄 轻量级锁 00
指向栈中锁记录的指针 重量级锁 10
指向互斥量的指针 GC标记 11
空
- 存储内容:
-
执行
<init>
方法- 按程序员意图初始化对象(显式赋初值、执行构造方法)。
二、对象大小与指针压缩
-
测量工具
<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.9</version> </dependency>
ClassLayout layout = ClassLayout.parseInstance(new Object()); System.out.println(layout.toPrintable());
-
指针压缩(Compressed Oops)
- 启用参数:
-XX:+UseCompressedOops
(默认开启),禁用:-XX:-UseCompressedOops
。 - 作用:
- 64位系统下用32位指针,减少内存占用(降低约1.5倍)。
- 支持最大堆内存 32GB(超过时指针压缩失效,需用64位地址)。
- 场景:
- 堆内存 < 4GB:无需压缩。
- 堆内存 ≤ 32GB:启用压缩。
- 启用参数:
三、对象内存分配策略
-
栈上分配
- 条件:
- 开启逃逸分析(
-XX:+DoEscapeAnalysis
,默认开启)。 - 开启标量替换(
-XX:+EliminateAllocations
,默认开启)。
- 开启逃逸分析(
- 优化场景:对象未逃逸出方法作用域(如方法内临时对象)。
- 条件:
-
Eden区分配
- 对象优先在Eden区分配,空间不足触发 Minor GC。
- Survivor区分配:
- 默认比例
Eden : Survivor = 8:1:1
。 - 关闭比例自适应:
-XX:-UseAdaptiveSizePolicy
。
- 默认比例
-
大对象直入老年代
- 参数:
-XX:PretenureSizeThreshold=字节大小
(仅Serial/ParNew收集器有效)。 - 目的:避免大对象复制开销。
- 参数:
-
长期存活对象晋升老年代
- 年龄计数器:每熬过1次Minor GC年龄+1。
- 晋升阈值:
- 默认
-XX:MaxTenuringThreshold=15
(CMS收集器默认为6)。
- 默认
-
动态年龄判断
- Survivor区中同年龄对象总大小 > Survivor区50%时,≥该年龄的对象晋升老年代。
- 参数:
-XX:TargetSurvivorRatio
(指定占比阈值)。
-
老年代空间分配担保
- 流程:
- Minor GC前检查老年代剩余空间是否 > 年轻代对象总大小。
- 若不足,检查
-XX:-HandlePromotionFailure
参数。 - 老年代空间 < 历次晋升平均大小,触发 Full GC。
- 流程:
四、对象内存回收
-
判断对象死亡
- 引用计数法:存在循环引用问题(JVM未采用)。
- 可达性分析法:从GC Roots(栈局部变量、静态变量等)向下扫描引用链。
-
引用类型
类型 特点 强引用 普遍对象引用( Object obj = new Object()
)软引用 SoftReference
包裹,内存不足时回收(适合缓存)弱引用 WeakReference
包裹,下次GC必回收虚引用 PhantomReference
包裹,用于对象回收跟踪(极少用) -
finalize() 自救机制
- 对象首次标记后,若重写
finalize()
且重新关联引用链,可避免回收(仅一次机会)。
- 对象首次标记后,若重写
五、类卸载条件
同时满足以下三点视为无用类:
- 堆中不存在该类的任何实例。
- 加载该类的
ClassLoader
已被回收。 - 该类对应的
java.lang.Class
对象未被引用,无法通过反射访问。
注:关键参数总结
- 指针压缩:
-XX:+UseCompressedOops
- 逃逸分析:
-XX:+DoEscapeAnalysis
- 标量替换:
-XX:+EliminateAllocations
- 年龄阈值:
-XX:MaxTenuringThreshold=15
- 大对象阈值:
-XX:PretenureSizeThreshold=1000000