类的生命周期
- 1. 类的生命周期
- 1.1 加载
- 1.2 连接
- 1.2.1 验证
- 1.2.2 准备
- 1.2.3 解析
- 1.3 初始化(静态模块执行,执行一次)
- 1.3.1 类的初始化时机(类的主动使用):
- 1.3.2 类的初始化顺序:
- 1.4 使用
- 1.4.1 对象实例化(实例化之后可以被正常使用)
- 1.5 卸载
JVM的类加载流程就是解析java的class文件,将其加载到内存中,并对数据进行校验、转换解析和初始化;最终变成可以直接被JAVA虚拟机使用的数据。
1. 类的生命周期
java为了支持运行时绑定,解析过程在某些情况下是在初始化之后才开始的,其他过程是不可变的。
1.1 加载
- 通过全限定类名获取这个类的二进制文件流。
- 将这个字节流所代表的静态存储结构转化成方法区的运行时结构。
- 在内存中生成一个代表这个类的java.lang.String 对象,作为访问这个类的的入口。
1.2 连接
1.2.1 验证
验证是连接过程的第一步,主要保证这些数据符合java虚拟机的规范,不会危害到虚拟机自身的安全。
- 文件格式验证:魔数、主版本号、次版本号是否在虚拟机的处理范围之内,常量是否合理,字节流是否可以正常解析。
- 元数据验证:父类与子类间的继承链是否正常,比如方法、字段是否冲突,子类是否实现了父类接口中要求需要实现的方法。是否存在不合法的元数据。
- 字节码验证:通过数据流和控制流分析,确保程序语义合法、符合逻辑,跳转指令在方法体之内。
- 符号引用验证:保证可以将符号引用转化成直接引用。
1.2.2 准备
为类变量分配内存并设置初始化值,这些变量所使用的内存都将在方法区中进行分配。
1.2.3 解析
虚拟机将常量池内的符号引用替换为直接引用的过程。
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类符号引用进行
1.3 初始化(静态模块执行,执行一次)
1.3.1 类的初始化时机(类的主动使用):
- 创建类的实例;
- 访问某个类或接口的静态变量(无重写的变量继承,变量其属于父类,而不属于子类),或者对该静态变量赋值(静态的read/write操作);
- 调用类的静态方法;
- 反射(如:Class.forName(“com.test.Test”));
- 初始化一个类的子类(Chlidren 继承了Parent类,如果仅仅初始化一个Children类,那么Parent类也是被主动使用了);
- Java虚拟机启动时被标明为启动类的类(换句话说就是包含main方法的那个类,而且本身main方法就是static的);
- JDK1.7开始提供的动态语言的支持:java.lang.invoke.MethodHandle实例的解析结果REF_getStatic,REF_public,REF_invokeStatic句柄对应的类没有初始化,则初始化。
1.3.2 类的初始化顺序:
package com.jon;
/**
* 有继承关系的类初始化顺序
* @author suym
* @date 2021/7/23
*
*/
class Parent {
// 静态变量
public static String P_STATIC_FIELD = "父类--静态变量";
protected int i = 1;
protected int j = 8;
// 变量
public String pField = "父类--变量";
// 静态初始化块
static {
// 静态变量必定在静态代码块之前
System.out.println(P_STATIC_FIELD);
System.out.println("父类--静态初始化代码块");
}
// 初始化块
{
// 变量必定在初始化代码块之前
System.out.println(pField);
System.out.println("父类初始化代码块");
}
// 构造器
public Parent() {
// 构造方法与构造代码块之间谁在前,谁在后
System.out.println("父类--构造器");
System.out.println("i=" + i + ", j=" + j);
j = 9;
}
}
public class SubClass extends Parent {
// 静态变量
public static String S_STATIC_FIELD = "子类--静态变量";
// 变量
public String sField = "子类--变量";
// 静态初始化块
static {
// 静态变量必定在静态代码块之前
System.out.println(S_STATIC_FIELD);
System.out.println("子类--静态初始化块");
}
// 初始化块
{
// 变量必定在初始化代码块之前
System.out.println(sField);
System.out.println("子类--初始化块");
}
// 构造器
public SubClass() {
// 构造方法与构造代码块之间谁在前,谁在后
System.out.println("子类--构造器");
System.out.println("i=" + i + ",j=" + j);
}
// 程序入口
public static void main(String[] args) {
new SubClass();
}
}
1.4 使用
类的使用分为主动使用和被动使用,主动使用的时候也是初始化的时机。
主动使用会进行初始化,被动使用不会进行初始化,比如:调用ClassLoader类的loadClass()方法加载一个类,并不是对类的主动使用,不会导致类的初始化。
1.4.1 对象实例化(实例化之后可以被正常使用)
类初始化之后才能进行对象实例化,对象实例化是对象生命周期的开始。两者区别如下:
类初始化:是完成程序执行前的准备工作。在这个阶段,静态的(变量,方法,代码块)会被执行。同时在会开辟一块存储空间用来存放静态的数据。初始化只在类加载的时候执行一次。
类的实例化:是指创建一个对象的过程。这个过程中会在堆中开辟内存,将一些非静态的方法,变量存放在里面。在程序执行的过程中,可以创建多个对象,既多次实例化。每次实例化都会开辟一块新的内存
1.5 卸载
一个类如果满足以下条件时,类就会被卸载
- 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
- 加载该类的ClassLoader已经被回收。
- 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。