JVM中类加载的过程
前面看了类加载的时机,本文来记录下类加载的过程,也就是加载的每个阶段都做了哪些事情
类的生命周期
加载
"加载"是类加载过程中的一个阶段,在这个阶段虚拟机做了3件事
- 通过一个类的全限定名获取定义此类的二进制流
- 通过这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的 java.lang.Class对象,作为方法区这个类的各种数据访问的入口。
注意,加载阶段与连接阶段的部分内容是交叉进行,加载阶段尚未完成,连接阶段可能已经开始了,但总体的顺序还是先加载再连接。
验证
验证阶段是连接阶段的第一步,这个阶段的目的是确保Class文件的字节流中包含的信息符合当前虚拟机的要求。不会危害虚拟机自身。验证的内容包含如下4个阶段
-
文件格式验证
验证字节流的格式是否符合class文件的规范及是否能被当前虚拟机处理。
a.是否已魔数0xCAFEBABE开头
b.主次版本号是否在当前虚拟机处理范围之内
c.常量池的常量中是否有不被支持的常量类型tag标志
d.指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量
e.CONSTANT_Utf8_Info型的常量是否有不符合UTF-8编码的数据
f.Class文件中各个部分及文件本身是否有被删除的或附加的其他信息
… -
元数据验证
语言分析,保证描述信息符合java语言规范要做
a.这个类是否有父类
b.这个类的父类是否继承了不允许继承的类(final修饰)
c.非抽象类,是否实现了父类及接口中的所有的抽象方法
d.类中字段,方法是否和父类产生矛盾
… -
字节码验证
本阶段是最复杂的阶段,通过数据流和控制流分析确定程序语义是否合法和符合逻辑。 -
符号引用验证
准备
本阶段也称为零值阶段,也就是将类中的类变量分配内存及赋初值,此处的初值是赋予对应类型的零值,如下
public static int value=123;
- 1
那么变量value的值在这个阶段赋予的是0而不是123,这里int为0,long为0L,boolean为false… …真正的初始化赋值是在初始化阶段进行的。同时要注意如果类变量被final修饰那么准备结果的结果就会不同
public final static int value=123;
- 1
编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为123.
解析
解析阶段就是将常量池内的符号引用替换为直接引用的过程。解析阶段包含以下内容。
- 类或接口的解析
- 字段解析
- 类方法解析
- 接口方法解析
初始化
在准备阶段已经对类变量赋值过一次了,当时是赋予的零值,而到了初始阶段则会根据我们主观计划去初始化类变量和其他资源,其本质初始化阶段是执行类构造器<clinit>方法的过程,在这个过程中有几个要注意的地方
- 静态语句块只能访问到定义在静态语句块之前的变量。可以给定义在之后的变量赋值但不可以访问
public class Test2 {
static{
i = 10; // 能赋值
System.out.println(i); // 但不能访问,提示 非法向前引用
}
static int i = 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- <clinit>不需要显示的调用父类的类构造器。虚拟机保证子类的<clinit>方法执行之前父类的<clinit>方法已经执行
- 由于父类先执行<clinit>方法,所以父类的静态语句块会优先于子类的静态语句块执行
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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
输出结果是2而不是1.
- <clinit>方法对于类或接口来说并不是必需的。如果一个类中没有静态语句块也没有对变量的赋值操作,那么编译器可以不为这个类生成<clinit>方法。
- 接口中不能使用静态语句块,任然有变量赋值操作,所以接口和类一样也会生成<clinit>()方法,但接口和类不同,接口中的<clinit>()方法不需要先执行父接口的<clinit>()方法,只有当父接口中定义的变量使用时父接口才会初始化。接口的实现类在初始化的时候一样不会执行<clinit>方法
- 同一个类只会被加载一次,/()方法也只会执行一次,如果多线程环境中只会有一个线程执行<clinit>方法,其他线程需要等待其执行完成。如果执行比较耗时那么会产生阻塞。
public class Test2 {
static {
if (true) {
System.out.println(Thread.currentThread().getName()+"开始初始化...");
while (true) {
// 死循环 阻塞
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
/**
* 测试
*
* @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();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
输出结果
Thread-0线程开始...
Thread-1线程开始...
Thread-0开始初始化...
- 1
- 2
- 3
一个线程在初始化,但死循环了,另一个线程只能等待。
参考《深入理解java虚拟机》
文章来源: dpb-bobokaoya-sm.blog.csdn.net,作者:波波烤鸭,版权归原作者所有,如需转载,请联系作者。
原文链接:dpb-bobokaoya-sm.blog.csdn.net/article/details/88381502
- 点赞
- 收藏
- 关注作者
评论(0)