在Java中,对象的创建方式及其在虚拟机中的具体过程如下:
一、对象的创建方式
-
使用
new
关键字
最常见的对象创建方式,直接调用类的构造方法。MyClass obj = new MyClass();
-
反射(Reflection)
通过Class
或Constructor
对象动态创建实例。// 使用Class.newInstance()(已废弃) MyClass obj = MyClass.class.newInstance(); // 使用Constructor.newInstance() Constructor<MyClass> constructor = MyClass.class.getConstructor(); MyClass obj = constructor.newInstance();
-
克隆(Clone)
实现Cloneable
接口,通过clone()
方法复制现有对象。MyClass original = new MyClass(); MyClass clone = (MyClass) original.clone();
-
反序列化(Deserialization)
通过反序列化字节流恢复对象,不会调用构造方法。ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj")); MyClass obj = (MyClass) in.readObject();
-
工厂方法(Factory Method)
通过静态方法返回对象实例,隐藏构造细节。public class MyClass { public static MyClass create() { return new MyClass(); } } MyClass obj = MyClass.create();
-
Unsafe 类(不推荐)
绕过构造方法直接分配内存(需谨慎使用)。MyClass obj = (MyClass) Unsafe.getUnsafe().allocateInstance(MyClass.class);
二、虚拟机中的对象创建过程
当 JVM 遇到 new
指令时,会按以下步骤创建对象:
1. 类加载检查
• 检查类是否已加载:
若类未被加载,触发类加载过程(加载 → 验证 → 准备 → 解析 → 初始化)。
2. 内存分配
• 分配堆内存:根据对象的类型和大小,在堆中分配空间。
• 分配方式:
| 方式 | 适用场景 | 实现 |
|---------------------|-----------------------------------------|----------------------------------------|
| 指针碰撞(Bump the Pointer) | 堆内存规整(如Serial、ParNew等带压缩的GC) | 通过移动指针分配连续内存。 |
| 空闲列表(Free List) | 堆内存不规整(如CMS) | 维护空闲内存块列表,从列表中分配。 |
• TLAB(Thread Local Allocation Buffer):
为每个线程在堆中预先分配一小块内存(默认1%),避免多线程竞争,提升分配效率。
3. 内存空间初始化
• 零值初始化:将对象的实例变量初始化为默认值(如 int
→ 0
,引用 → null
)。
4. 设置对象头(Object Header)
对象头包含以下信息:
• Mark Word:哈希码、GC分代年龄、锁状态标志等(32位/64位结构不同)。
• 类型指针:指向类元数据的指针(用于确定对象类型)。
• 数组长度(仅数组对象):记录数组长度。
5. 执行构造方法(<init>
)
• 实例变量初始化:执行代码中的显式赋值(如 private int x = 5;
)。
• 构造代码块:执行 {}
中的代码。
• 构造函数:执行用户定义的构造方法(如 public MyClass() { ... }
)。
三、不同创建方式的虚拟机处理差异
创建方式 | 内存分配 | 零值初始化 | 对象头设置 | 执行构造方法 |
---|---|---|---|---|
new 关键字 | ✅ | ✅ | ✅ | ✅ |
反射(Constructor) | ✅ | ✅ | ✅ | ✅ |
克隆(Clone) | ✅ | ❌(复制原值) | ✅ | ❌ |
反序列化 | ✅ | ❌(恢复原值) | ✅ | ❌ |
Unsafe.allocateInstance | ✅ | ✅ | ✅ | ❌ |
四、关键点总结
• 内存分配策略:指针碰撞(规整堆) vs. 空闲列表(碎片化堆)。
• TLAB 优化:减少多线程竞争,提升分配效率。
• 对象头作用:存储元数据(如锁状态、GC信息),支持运行时类型检查。
• 构造方法执行:对象创建的最后一步,完成用户定义的初始化逻辑。
理解这些机制有助于优化代码性能(如合理使用对象池)和排查内存问题(如对象头损坏导致的锁异常)。