对象创建的过程是什么
在 Java 中,通过 new
关键字创建对象的过程主要包括以下步骤:
1. 类加载:加载类信息到方法区。
2. 内存分配:在堆中为对象分配内存空间。
3. 初始化零值:将对象内存清零(赋默认值)。
4. 设置对象头:写入对象元数据(如类指针、锁状态)。
5. 执行构造器:调用 <init>
方法,初始化实例字段。
1. 详细步骤分解
(1) 类加载
- 触发条件:
- 首次使用类(如
new ClassName()
)。 - 过程:
- JVM 检查类是否已加载。
- 未加载则通过类加载器(ClassLoader)执行:
- 加载:读取
.class
文件到方法区。 - 链接:
- 验证:检查字节码合法性。
- 准备:为静态变量分配内存,赋默认值。
- 解析:符号引用转为直接引用。
- 初始化:执行
<clinit>
方法(静态块、静态变量赋值)。
- 加载:读取
- 结果:
Class
对象就绪,存储在方法区。
(2) 内存分配
- 位置:
- 堆内存(年轻代 Eden 区)。
- 方式:
- 指针碰撞(Bump Pointer):
- 假设堆内存连续,移动指针分配。
- 适合串行、CMS 收集器。
- 空闲列表(Free List):
- 记录可用内存块,分配合适空间。
- 适合并发收集器(如 G1)。
- 线程安全:
- CAS + 失败重试:多线程竞争分配。
- TLAB(Thread Local Allocation Buffer):线程私有缓冲区加速。
(3) 初始化零值
- 过程:
- 分配的内存清零,字段赋默认值。
- 默认值:
int
:0。boolean
:false。- 引用类型:
null
。 - 目的:
- 确保字段有定义状态,避免未初始化错误。
(4) 设置对象头
- 对象头(Mark Word):
- 包含:
- 类指针:指向方法区的
Class
对象。 - 锁状态:如无锁、偏向锁(用于
synchronized
)。 - 哈希码:调用
hashCode()
时生成。 - GC 分代年龄:垃圾回收标记。
- 类指针:指向方法区的
- HotSpot 示例(64 位):
无锁: | hashCode | age | 0 |
(5) 执行构造器
- 过程:
- 调用
<init>
方法(由编译器生成)。 - 执行:
- 父类构造器:递归调用
super()
。 - 实例字段初始化:赋初始值(如
int x = 10
)。 - 构造器代码:执行用户定义逻辑。
- 父类构造器:递归调用
- 字节码:
new #2 // 创建对象 (ClassName)
dup
invokespecial #3 // 调用 <init>
astore_1 // 存储引用
示例
class Person {
int age = 25; // 实例初始化
String name;
Person(String n) {
this.name = n; // 构造器赋值
}
}
Person p = new Person("Alice");
- 过程:
- 加载
Person
类。 - 分配内存(堆中)。
- 零值:
age = 0
,name = null
。 - 对象头:指向
Person
的Class
。 <init>
:age = 25
,name = "Alice"
。
2. JVM 视角
- 字节码指令:
new
:分配内存。dup
:复制引用。invokespecial
:调用构造器。- 内存布局:
- 对象头:8-16 字节(32/64 位)。
- 实例数据:字段值。
- 对齐填充:补齐至 8 字节倍数。
图示
堆内存:
[ 对象头 | age (4字节) | name (引用) | 填充 ]
3. 注意事项
- 异常:
- 类加载失败(如
ClassNotFoundException
)。 - 内存不足(
OutOfMemoryError
)。 - 性能:
- TLAB 优化多线程分配。
- JIT 编译消除冗余对象创建。
4. 延伸与面试角度
- 与 static 初始化:
<clinit>
:类加载时静态初始化。<init>
:对象创建时实例初始化。- 单例模式:
- 控制创建过程(如
double-checked locking
)。 - 实际应用:
- Spring Bean:反射创建对象。
- 面试点:
- 问“步骤”时,提五步流程。
- 问“内存”时,提指针碰撞和对象头。
总结
Java 对象创建从类加载到构造器执行,涉及内存分配、零值初始化、对象头设置等步骤。JVM 通过优化(如 TLAB、锁升级)提升效率。面试时,可提字节码或画内存图,展示理解深度。