Java中new的相关知识
在 Java 中,new
关键字是创建对象的核心方式,其本质是触发 JVM 在堆内存中为对象分配空间并完成初始化。这个过程涉及类加载、内存分配、初始化等多个环节,下面详细拆解其底层机制:
一、前置检查:类加载验证
在new
执行前,JVM 会先确认目标类是否已被加载到方法区(JDK8 + 为元空间)。如果类未加载,会触发类加载过程(加载→验证→准备→解析→初始化):
- 加载:通过类全限定名读取字节码文件(.class)到内存;
- 验证:确保字节码符合 JVM 规范(如格式正确、无安全隐患);
- 准备:为类的静态变量分配内存并设置默认零值(如
static int a
默认 0); - 解析:将符号引用(如类名、方法名)转换为直接引用(内存地址);
- 初始化:执行静态代码块和静态变量的显式赋值(如
static int a = 10
)。
只有类加载完成后,JVM 才知道该类的对象需要占用多少内存(实例变量的类型和数量已确定)。
二、核心步骤:堆内存分配
类加载完成后,new
的核心动作是在堆内存中为新对象分配空间,空间大小由类的实例变量(非静态字段)决定(如int
占 4 字节,Object
引用占 4/8 字节,依 JVM 位数而定)。
内存分配有两种主流方式,取决于堆内存是否连续:
1. 指针碰撞(Bump The Pointer)
适用于堆内存无碎片的场景(如采用 “标记 - 复制” 算法的 GC 收集器:Serial、ParNew 等)。
- 原理:堆中已用内存和空闲内存之间有一个指针,分配时只需将指针向空闲内存方向移动 “对象大小” 的距离,即可完成分配。
- 示例:假设指针当前指向地址 1000,新对象需 200 字节,则指针移动到 1200,1000~1200 的空间分配给新对象。
2. 空闲列表(Free List)
适用于堆内存有碎片的场景(如采用 “标记 - 清除” 算法的 GC 收集器:CMS)。
- 原理:JVM 维护一个 “空闲列表”,记录堆中所有空闲内存块的地址和大小。分配时从列表中找到一块足够大的空闲块,分割出 “对象大小” 的空间分配给新对象,剩余部分仍保留在列表中。
三、并发安全:解决分配冲突
多线程同时用new
创建对象时,可能出现 “指针移动冲突”(如两个线程同时操作同一指针)。JVM 通过两种方式保证线程安全:
1. CAS + 失败重试
对内存分配动作加同步锁:通过CAS(Compare And Swap) 机制检查指针是否被其他线程修改,若未修改则成功分配,若已修改则重试,直到成功。
2. TLAB(本地线程分配缓冲)
JVM 为每个线程在堆中预先分配一块私有内存(TLAB,Thread Local Allocation Buffer)。
- 线程创建对象时,优先在自己的 TLAB 中分配内存,避免线程间竞争;
- 当 TLAB 用完时,才通过 CAS 从堆的公共区域分配新的 TLAB。
- 可通过
-XX:+UseTLAB
开启(默认开启),-XX:TLABSize
设置大小。
四、内存初始化:零值填充
内存分配完成后,JVM 会将分配到的空间(除对象头外)全部初始化为零值(如int
→0,boolean
→false,Object
引用→null)。
- 这一步是 “默认值” 的来源:即使实例变量未显式赋值,也能保证有合理的初始状态(如
class A { int x; }
中,new A().x
默认 0)。
五、对象头设置(Object Header)
内存初始化后,JVM 会为对象设置对象头(占 8/16 字节,依 JVM 位数和对象类型而定),包含两类信息:
- 运行时数据:哈希码(HashCode)、GC 分代年龄(对象经历 GC 的次数)、锁状态标志(是否被同步锁持有)等;
- 类型指针:指向对象所属类的元数据(在方法区),用于确定对象的类型(如
obj.getClass()
的底层依据)。
特殊情况:如果是数组对象,对象头还会额外存储数组长度(因为数组的大小在编译期不确定,需运行时记录)。
六、执行构造方法(<init>)
最后,JVM 会调用对象的构造方法(字节码层面的<init>
方法),完成以下操作:
- 执行实例变量的显式赋值(如
int x = 10
); - 执行构造方法中的代码逻辑(如初始化其他对象、计算等)。
至此,一个完整可用的对象创建完成,new
表达式返回该对象在堆中的引用(存储在栈中,或作为其他对象的字段)。
总结:new
分配内存的完整流程
- 检查类是否加载→未加载则触发类加载;
- 在堆中分配内存(指针碰撞 / 空闲列表);
- 解决并发冲突(CAS 重试 / TLAB);
- 内存空间零值初始化;
- 设置对象头(运行时数据 + 类型指针);
- 执行构造方法(<init>),返回对象引用。
这个过程由 JVM 自动管理,开发者无需手动操作内存,体现了 Java “内存安全” 的特性。