类加载过程图解:
简介:
以上这张图是博主经过阅读周志明的深入理解Java虚拟机一书,并在网上看了若干帖子之后画出来的,我争取用一句话来
描述每个阶段都做了什么,因为每个阶段做的事情实在有一些多,而我们做为开发者也着实想理解的深入一些,但是我尽量用少一些理论文字,多一些图解和案例来说明每个阶段自己的理解。
首先澄清一点。基于博主的理解有限,加上主观判定有可能错误。所以欢迎大家指正。一起学习,我会及时的更新本篇纠正错误。
编译阶段
编译阶段严格意义上来说,并不是类加载阶段中的一环,它是由Java Code转换成class字节码的一部分。关于class文件结构的说明,在书中也有提到,class文件是严格紧凑的,在字节码文件中,从第一位开始,到最后一位结束,按照既有的文件结构依次往下捋顺即可。
编译阶段,值得一提的是静态资源的引用,例如:
class A{
public static final int a = 1;
}
class B{
public static final int b = A.a
}
案例我没有测试,意思很明确,如果在类B中引用了类A的静态变量,那么编译完成后。在类B的class字节码常量池中会
有b=1这样的编译案例,也就是说。在编译阶段结束后。静态变量的引用,将会与原来的类彻底无关!
Class文件结构
一张简单的图,来说明java到class文件结构的转换。本篇不做过多说明
加载阶段
通过一个类的全限类名获取定义此类的二进制流,
将这个字节流所代表的静态存储结构,转换成为方法区的运行时数据结构
在堆中生成代表这个类的class对象,开放出去作为数据的访问入口。
验证阶段
此阶段不做过多说明,验证文件合法性逻辑,理解即可。
文件格式验证:class文件结构验证,比如开头的魔数,版本号,常量池等等
元数据验证:继承或实现,是否实现了abstract方法或者是接口中的方法?
字节码验证:泛型,类型转换是否有效等等
符号引用验证:除了自身常量池之外的引用,比如权限类名是否可以被找到
准备阶段
变量内存初始化,并且赋初始化值,final类型为直接赋值,不必在等初始化阶段
解析阶段
此阶段还有静态解析,和动态解析一说。当接口类型,或者方法类型不确定时,则为动态解析
动态解析必须要等到运行code时,才能够解析出来
初始化阶段
简而言之,就是执行字节码指令。一般为static变量和代码块或者加载器的声明或逻辑
使用new,getstatic,putstatic或invokestatic字节码指令的时候
使用反射类对象实例的时候
当初始化一个类,发现其父类还没有初始化,则首先初始化其父类
main函数的类,必然首先被初始化
当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getstatic,REF_putstatic,REF_invokestatic方法句柄,并且这个方法句柄所对象的类,没有进行过初始化,则首先触发其初始化.
5个情况对应的字节码指令
anewarray | checkcast | getfield | getstatic |
---|---|---|---|
instanceof | invokespecial | invokestatic | invokevirtual |
ldc | ldc_w | multianewarray | new |
putfield | putstatic |
被动引用不会触发立即初始化的2种情况
public class SuperClass {
static{
System.out.println("Super class Init");
}
public static int value = 3;
}
public class SubClass extends SuperClass{
static{
System.out.println("SubClass Init");
}
}
public class NotInitialialization {
/****
* 被动引用一:引用父类初始化
* 通过子类引用父类,只会触发父类的初始化阶段,而不会触发子类的初始化阶段
* 输出结果为
* Super class Init
* 3
*/
public static void main(String[] args) {
System.out.println(SubClass.value);
}
/****
* 被动引用二:数组初始化
* 被动引用案例 数组案例
* 并不会触发SuperClass的初始化阶段
* 实际为 new Array,和类本身无关
* @param args
*/
public static void main(String[] args) {
SuperClass[] sca = new SuperClass[10];
}
}
类加载总结:
类加载阶段开始顺序不变,加载,验证,准备,初始化,卸载。这5个阶段的顺序是依次开始的。
类加载各阶段进行是交叉执行的。
一切以class字节码描述为依据,执行相关流程
加载器的双亲委派模型
图中指定了每个加载器负责加载的目录.所谓双亲委派,也就是先让父类先加载,可以理解Bootstrap是Extension的爸爸,Extension对于Application亦然。而我们自己编写的类加载器,这三位加载器的后代了。说白了,加载一个类,任务给到你,你自己不加载,先给爸爸,爸爸给爷爷,爷爷给太爷爷。一个概念。但是不一样的是,这几个加载器每个只加载自己固定的范畴,不属于自己任务的,他们不会加载。如果三个加载器走完,都没有检索到要加载的类,那么就会向下调用到自己这里来,自己才会去尝试加载。这样的加载机制避免了重复加载
破坏双亲委派
类似与JDBC,Java定义标准,由厂商负责实现,如果采用双亲委派,那么BootStrap检查到Driver类的实现类的时候,它无法判定究竟是哪个类,包括mysql,oracle,sql server 每一个都有自己的实现。由此为了保证类加载器能够加载实现类,双亲委派模型被破坏了。
热部署模块,使得模块可以拆卸,热插拔。OSGi被提了出来。