目录
[指针碰撞(Bump The Pointer)](#指针碰撞(Bump The Pointer))
[空闲列表(Free List)](#空闲列表(Free List))
创建对象的方式
new关键字
通过 new
关键字调用类的构造方法创建对象,是最直接、最常见的方式。
java
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
}
// 创建对象
Person person = new Person("Alice");
反射
java
// 方式1:通过 Class 类的 newInstance()(已过时,推荐用 Constructor)
Class<Person> clazz = Person.class;
Person person1 = clazz.newInstance(); // 调用无参构造
// 方式2:通过 Constructor 类(支持有参/私有构造)
Constructor<Person> constructor = clazz.getConstructor(String.class);
Person person2 = constructor.newInstance("Bob");
Class类的newInstance()方法
- 只能调用类的无参构造方法 ,且要求该构造方法必须是可访问的 (非
private
)。 - 若类没有无参构造方法,或无参构造方法是私有的,调用时会直接抛出
InstantiationException
或IllegalAccessException
。
Constructor类的newInstance()方法
- 支持调用类的任意构造方法(包括无参、有参、甚至私有构造方法)。
- 只需通过
Class.getConstructor(参数类型...)
或Class.getDeclaredConstructor(参数类型...)
获取对应构造器,再传入参数即可。 - 对于私有构造方法,可通过
Constructor.setAccessible(true)
突破访问权限限制。
clone()
通过对象的 clone()
方法创建其副本,无需调用构造方法,属于浅拷贝(默认)。
java
public class Person implements Cloneable {
private String name;
// 重写 clone() 方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
// 创建对象副本
Person original = new Person("Charlie");
Person clone = (Person) original.clone();
反序列化
将序列化(如通过 ObjectOutputStream
写入磁盘)的对象字节流恢复为内存对象,无需调用构造方法。
java
import java.io.*;
public class Person implements Serializable {
private String name;
// 序列化
public static void serialize(Person p) throws IOException {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.obj"))) {
oos.writeObject(p);
}
}
// 反序列化(创建对象)
public static Person deserialize() throws IOException, ClassNotFoundException {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.obj"))) {
return (Person) ois.readObject();
}
}
}
// 使用
Person original = new Person("David");
Person.deserialize(); // 从文件恢复对象
创建对象的流程
创建对象通常都是用 new 关键字创建,但在虚拟机中远比表面复杂。接下来将对对象创建流程作详细解析。
类加载检查
当 JVM 检测到字节码 new指令 时,会先对其进行类加载检查 。查看指令参数在常量池中是否对应一个类的符号引用 ,并检查这个类的符号引用有没有经历过类加载的流程。如果没有,那必须先执行相应的类加载过程。
内存分配
类加载通过后,对应类的对象大小也被确定下来。接下来将要从堆中划分出一定大小的空间块用来存储类的实例。
指针碰撞(Bump The Pointer)
假设堆是规整的,不存在空间碎片,那么可以直接通过指针碰撞来进行对象空间的分配。
所谓指针碰撞,就是将堆顶上移一定的空间,用来存储类的实例。

空闲列表(Free List)
但当堆空间是不规整的,已被使用的内存和空闲的内存相互交错在一起,即存在空间碎片,那么必须维护一个空闲列表,其中存放着可用的内存块并记录其大小。当新的对象实例请求分配空间时,就会访问空闲列表然后找到足够大的空闲内存块。

并发问题
对象创建在并发情况下也并不是线程安全的,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。解决这个问题有两种可选方案:
- 分配空间时通过CAS + 失败重试保证分配空间的原子性,直到分配到可用空间。
- 通过不同线程对应的 TLAB 来分配不同线程创建的对象。哪个线程要分配内存,就在哪个线程的本地缓冲区中分配,只有本地缓冲区用完了,分配新的缓存区时才需要同步锁定。
初始化零值
为新对象分配完一块连续的内存空间后,会自动将这块内存中除了对象头(Object Header)之外的所有字节都初始化为零值 (如 0
、0L
、null
、false
等,对应不同数据类型的默认值)。因此对象的实例字段可以不赋初始值就直接使用。
如果使用了TLAB的话,这一项工作也可以提前至TLAB分配时顺便进行。
对象头设置
接下来对对象头做必要的设置。例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码(实际上对象的哈希码会延后到真正调用Object::hashCode()方法时才计算)、对象的GC分代年龄等信息。
<init>方法
在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了。但是对象实例的字段值依然为0,依次还要执行构造方法 :虚拟机调用对象的<init>()方法(即构造函数的字节码表现形式),执行开发者定义的初始化逻辑 ------ 包括显式字段赋值、代码块执行、父类构造函数调用等。这一步将对象从 "零值状态" 调整为 "业务预期状态"。
这样一个真正可用的对象才算完全被构造出来。
总结
