Java 类生命周期
类加载过程
类加载主要有三步:加载 - 链接 - 初始化
。
🥎 加载
加载是一个读取 Class 文件,将其转化为某种静态数据结构存储在方法区内,并在堆中生成一个便于调用的 java.lang.Class
类型的对象的过程。
分为三步进行:
获取定义类的二进制字节流(不限制从哪里获取,可以从文件、网络、即时生成)
将字节流代表的静态存储结构,转换成方法区中运行时数据结构
在堆中生成一个代表该类的
java.lang.Class
对象,作为方法区这个类的各种数据访问入口
🥎 链接
⚾️ 验证
文件格式验证
验证字节流是否符合 Class 文件格式的规范,能被当前版本的虚拟机处理。发生在加载阶段。
元数据、字节码验证
对字节码进行语法、语义的分析,保证其符合 Java 虚拟机的规范,不会有危害虚拟机的行为。
符号引用验证
确保解析行为能够正常执行。发生在解析阶段。
⚾️ 准备
为类中定义的变量(静态变量 static
)分配内存并设置类变量零值的阶段。
static
-> 赋零值static final
-> 赋定义的常量值
⚾️ 解析
解析是将常量池中的符号引用替换为直接引用。
符号引用:描述引用对象的符号
直接引用:指向目标实际地址的指针
🎾 解析部分是灵活的,可以在初始化环节后再进行,实现所谓的“后期绑定”。(方法调用直到运行时才会解析,因为无法在编译时确定方法调用所需的所有信息,所以方法定义和方法调用直到运行时才绑定。)
当一个类被编译成 Class 之后,假设这个类称为 A
,并且在类 A
中引用了类 B
。
在编译阶段,
A
无法确定B
是否被编译(现在B
一定未加载),此时A
无法知道B
的实际地址,所有在A.Class
中,会使用一个字符串
代表B
,这个字符串被称为符号引用。在运行阶段,
A
发生了加载,在解析时,发现其中的B
还未被加载,就会触发类B
的加载,将B
加载到虚拟机中。此时,A
中的符号引用会被替换为B
中的实际地址(也就是直接引用)。
静态解析
类
B
是具体的实现类,解析的对象十分明确,即会进行静态解析。动态解析
Java 通过后期绑定的方式实现多态,通过动态解析实现。
如果类
B
是抽象类或者接口,有具体的实现类C
、D
,当前具体的实现方式并不明确,无法确定使用哪个具体实现。直到运行过程中发生了调用,虚拟机调用栈中会得到具体类的信息,再进行解析,就有明确的直接引用代替符号引用。
🥎 初始化
虚拟机真正开始执行类中编写的代码,完成一些主动的资源初始化动作。
执行的是类层面的初始化。 只有显式调用 new 才会执行构造函数的初始化。
- 类变量(静态变量)的赋值
static
- 静态代码块
static { }
如何判断无用类
- 该类的所有实例对象全部被 GC,在堆中不存在该类的任何实例对象
- 该类对应的
java.lang.Class
对象,在任何地方都没有被引用 - 该类的类加载器已经被回收
满足以上三个条件,说明该类可以被回收,但是与对象不同,无用类不一定会被回收。