类加载Class类型的文件主要三步:加载->链接->初始化
。链接过程又可以分为三步:验证->准备->解析
。
全文的思维导图
加载
类加载阶段主要完成如下三件事
- 通过全类名,获取定义此类的二进制字节流。
- 解析类的二进制流为方法区的数据结构。
- 在内存中创建一个代表该类的Class的对象,作为方法区这个类的访问入口。
该阶段可以大致如图所示:
链接
主要分为三个阶段:验证、准备、解析
验证
目的是确保加载的class文件的字节流中包含信息符合当前虚拟机的要求,运行时不会危害虚拟机的自身安全。
四种验证:文件格式验证,元数据验证,字节码验证,符号引用验证。
准备
主要是为类的静态变量分配内存,并初始化为默认初始值。即零值,这个0值是根据数据类型默认的0值,如果是boolean,则初始化为false。
如果类静态变量的字段属性表中存在constant Value属性,则直接执行赋值语句。
- 类静态变量为基本数据类型,并且被final修饰。
- 类静态变量为String类型,被final修饰,并且以字面量的形式赋值
下面我们用一个例子验证上面的说法:
/**
* @author yanshuang
* @date 2022/1/5 9:17 下午
*/
public class constantValueTest {
private static int age = 10;
private static final int length = 10;
private static final String name = "zhangsan";
private static final String s = new String("s");
}
我们查看编译后的class文件字节码
可以看到属性length
和name
存在ConstantValue,并且在此阶段直接赋值。
解析
将类、方法、字段和方法的符号引用(在常量池中)转为直接引用。
符号引用:用一组符号来描述所引用的目标
直接引用:直接指向目标的指针
初始化
执行类静态成员赋值语句和静态代码块中的语句。
执行类构造方法
测试
/**
* @author yanshuang
* @date 2022/1/6 10:40 上午
*/
public class clinitTest {
private String name;
private int age = 10;
private static int gender = 1;
{
System.out.println("构造代码块");
}
static {
System.out.println("静态代码块");
}
public clinitTest () {
System.out.println("构造方法");
}
public String getName() {
return this.name;
}
}
可以看到字节码中包含了3个方法,getName我们都知道,那么和方法里面执行了哪些逻辑?
从字节码角度分析一下:
方法:
从字节码可以看到方法的主要逻辑为
- 调用父类的方法
- 非静态成员变量赋值
- 执行构造代码块
- 执行构造函数
方法
从字节码可以看到方法的主要逻辑为
- 执行静态方法的赋值语句
- 执行静态代码块中的语句
- 需要注意一点是,Java虚拟机会保证子类的方法执行前,父类的方法已经执行完毕
静态代码块,构造方法,构造函数的执行顺序?
没有继承关系情况的执行顺序
- 静态代码块和静态成员变量,执行顺序由编写顺序决定(只会执行一次)
- 构造代码块和非静态成员变量,执行顺序由编写顺序决定。
- 构造函数
有继承情况的执行顺序
- 父类的静态(静态代码块,静态成员变量),子类的静态(静态代码块,静态成员变量)
- 父类的非静态(构造代码块,非静态成员变量),父类的构造方法
- 子类的非静态(构造代码块,非静态成员变量),子类的构造方法