1. 类初始化和类加载
1.1 创建对象的过程
在 JVM 中,创建一个新对象并非简单的内存分配,它包含几个关键步骤。当执行到 new 关键字时,JVM 会先检查目标类是否已加载。若未加载,则触发类加载流程;若已加载,则继续后续步骤。
接下来是在堆内存中分配空间,随后将对象实例变量初始化为零值(对象头除外)。之后需要对对象头进行必要的设置,比如记录哈希码、分代年龄等信息。最后,才会执行对应的构造方法(init 方法)。
- 类加载:JVM 在执行到 new 关键字的时候,会先进行对应类的类是否加载,如果没有加载,那么就会先执行类加载,然后再执行 new 关键字。
- 分配内存空间:在堆中分配一段空间给对应对象。
- 初始化零值操作:给对象的空间内的对应成员初始化为零值(对象头除外)。
- 进行必要的设置:比如说:给对象头设置一些信息,类的元数据,对象的年龄,对象的哈希码。
- 执行 init 方法:执行对应的构造方法。
1.2 对象的生命周期
对象的生命周期可以概括为创建、使用、销毁三个阶段。
- 创建:即上述的类加载与对象实例化过程。
- 使用:调用该对象的成员方法或访问属性。
- 销毁:当对象没有被引用了,就会被看作垃圾进行回收。
1.3 类加载器
JVM 提供了三种主要的类加载器,它们各司其职:
- 启动类加载器:负责加载 Java 核心库里面的类,通常由 C++ 实现。
- 扩展类加载器:加载一些扩展的类,比如说框架的类。
- 应用程序类加载器:加载用户自己手动创建的类,也就是我们日常开发中最常接触的。
1.4 双亲委派模型
这是类加载器的核心原则,遵循'向上委派,向下加载'的策略。
如果一个类加载器收到了加载类的任务,不会自己先执行,而是往上委派给自己的父类,依次类推,直到委派给最终的父类也就是启动类加载器。然后从启动类加载器开始,判断该类是否被自己加载,如果能直接返回即可,如果不能继续往下加载(抛给子类),依次类推。
这种机制主要有以下作用:
- 保证类的唯一性:向上委派、向下加载的方式,保证了一个类不会被重复加载,并且保证一个类只会被一个类加载器加载。
- 保证安全性:如果用户自己编写了一些类,类的全限定名与 Java 核心库中的类一致,可能会出现优先加载用户的类,从而间接导致核心类被用户修改了。通过向上委派、向下加载的方式从上开始加载,保证了类的安全性。
- 形成了隔离和分层的效果:不同的类加载器负责不同的类,既不会重复加载,也保证了高效性。
- 简化了类加载流程:这种方式保证了一个类只能加载一次并且不同的类加载器负责不同的类,从而简化了流程。
1.5 类加载的过程
从大体上类加载分为五步:类加载、连接、初始化、使用、卸载。细分又有七步:类加载、验证、准备、解析、初始化、使用、卸载。
- 类加载:通过类的全限定名找到对应的二进制字节码文件,将该文件转换到内存形成创建对象实例的模板。
- 验证:验证该二进制字节码是否符合 JVM 规范,比如说校验魔术、主版本号等等。具体包括文件格式校验、元数据验证、字节码验证、符号引用验证。
- 准备:给该类的静态变量赋一些初始化值。
- 解析:将原来的符号引用转为直接引用。
- 初始化:执行构造器方法,执行父类的静态成员、子类的静态成员、父类的构造方法、子类的构造方法。
- 使用:用户使用。
- 卸载:判断该类没有被引用即可卸载,主要看三个方面:该类里面的所有实例对象都已经被回收了、该类的 ClassLoader 对象已经被回收了、该类的实例没有被引用。


