
类加载流程描述了类加载器把 class 文件加载到虚拟机的过程。class 文件像是一个模板,被加载到 JVM 之后,可以被复刻为多个实例。
类加载流程包括 “加载“,”链接“,”初始化“ 三个步骤, 其中 ”链接“ 包括了 ”验证“,”准备“,”解析“三个小步骤,最终加载到方法区。
加载类加载过程大致可描述为三步:
package test
class World {
}
JVM 加载类之前会提前判断该类是否已经被加载。如果被加载了,则直接跳过了加载流程。
链接”链接“ 包括了 ”验证“,”准备“,”解析“三个小步骤。
验证验证的目标主要是保证加载之后的 class 二进制字节流数据符合虚拟机的要求,保证被加载类的正确性,不会危害虚拟机的自身安全。验证的内容主要包括四种:
准备阶段主要完成对类变量的初始化:
package test
class World {
private static int a = 1; // 在准备阶段只是做默认初始化,赋值为 0
public static void main(String[] argv) {
System.out.println(a);
}
}
需要注意的是:在准备阶段不会对实例变量做初始化,实例变量是在实例化的时候进行初始化,并且是在堆空间,而类变量是在方法区。
解析解析过程主要负责将常量池中的符号引用转化为直接引用。
初始化初始化过程实际上是执行类构造器方法 clinit 的过程。该方法不需要定义,是 javac 根据类中所有的类变量的显示赋值和静态代码块汇总而成。
注意:clinit 和 init 方法不同,init 方法是根据类的构造器编译生成的。
package test
class World {
private static int a = 1; // 在准备阶段只是做默认初始化,赋值为 0
static {
a = 10;
b = 20;
}
private static int b = 10;
public static void main(String[] argv) {
System.out.println(World.a);
System.out.println(World.b);
}
}
在链接的准备阶段,类变量 a 和 b 已经被分配内存并且进行默认初始化,在初始化阶段,按照语句在文件中出现的顺序执行,b 会被赋值为 20,然后再被赋值为 10;从字节码中中可以得到验证。
package test
class World {
private int b = 10;
public static void main(String[] argv) {
int a = 20;
}
}
从编译之后的字节码来看,并没有 clinit 方法,也就不存在执行 clinit 过程。
package test;
public class World {
static class Fu {
public static int a = 10;
static {
a = 20;
}
}
static class Zi extends Fu {
public static int b = 1;
static {
b = a;
}
}
public static void main(String[] argv) {
System.out.println(Zi.b);
}
}
加载 Zi 类的时候会提前加载 Fu 类,会执行 Fu 类的 clinit 方法(按顺序执行),此时 a = 20;再加载 Zi 执行 clinit 方法(按顺序执行)b = a = 20。
package test;
class Hello {
static {
System.out.println("Hello 被加载了");
}
}
public class World {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run() {
Hello hello = new Hello();
System.out.println(Thread.currentThread().getName() + "执行");
}
};
Thread thread1 = new Thread(runnable, "1");
Thread thread2 = new Thread(runnable, "2");
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
}
尽管多个线程同时使用了 Hello 类,尝试了加载,但是静态代码块中的 "Hello 被加载了" 只是被打印了一次。
Hello 被加载了 1执行 2执行