前面看了类加载的时机,本文来记录下类加载的过程,也就是加载的每个阶段都做了哪些事情
"加载"是类加载过程中的一个阶段,在这个阶段虚拟机做了3件事
注意,加载阶段与连接阶段的部分内容是交叉进行,加载阶段尚未完成,连接阶段可能已经开始了,但总体的顺序还是先加载再连接。
验证阶段是连接阶段的第一步,这个阶段的目的是确保Class文件的字节流中包含的信息符合当前虚拟机的要求。不会危害虚拟机自身。验证的内容包含如下4个阶段
本阶段也称为零值阶段,也就是将类中的类变量分配内存及赋初值,此处的初值是赋予对应类型的零值,如下
public static int value=123;
那么变量value的值在这个阶段赋予的是0而不是123,这里int为0,long为0L,boolean为false… …真正的初始化赋值是在初始化阶段进行的。同时要注意如果类变量被final修饰那么准备结果的结果就会不同
public final static int value=123;
编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为123.
解析阶段就是将常量池内的符号引用替换为直接引用的过程。解析阶段包含以下内容。
在准备阶段已经对类变量赋值过一次了,当时是赋予的零值,而到了初始阶段则会根据我们主观计划去初始化类变量和其他资源,其本质初始化阶段是执行类构造器<clinit>方法的过程,在这个过程中有几个要注意的地方
public class Test2 {
static{
i = 10; // 能赋值
System.out.println(i); // 但不能访问,提示 非法向前引用
}
static int i = 0;
}
public class Test2 {
static class Parent{
static int A = 1;
static{
A = 2;
}
}
static class Sub extends Parent{
static int B = A;
}
public static void main(String[] args) {
System.out.println(Sub.B);
}
}
输出结果是2而不是1.
public class Test2 {
static {
if (true) {
System.out.println(Thread.currentThread().getName()+"开始初始化...");
while (true) {
// 死循环 阻塞
}
}
}
}
/**
* 测试
*
* @author 波波烤鸭
* @email dengpbs@163.com
*
*/
public class Test {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始...");
Test2 t = new Test2();
System.out.println("线程结束...");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始...");
Test2 t = new Test2();
System.out.println("线程结束...");
}
}).start();
}
}
输出结果
Thread-0线程开始...
Thread-1线程开始...
Thread-0开始初始化...
一个线程在初始化,但死循环了,另一个线程只能等待。
参考《深入理解java虚拟机》