阅读 35

深入理解JVM:类加载过程 及 双亲委派模型

类的生命周期

类从被加载到内存中开始,到卸载出内存,经历了加载、连接、初始化、使用四个阶段,其中连接又包含了验证、准备、解析三个步骤。这些步骤总体上是按照图中顺序进行的,但是Java语言本身支持运行时绑定,所以解析阶段也可以是在初始化之后进行的。以上顺序都只是说开始的顺序,实际过程中是交叉进行的,加载过程中可能就已经开始验证了。

类加载过程

在这里插入图片描述

1、加载

“类加载” 过程比较多,而加载是其中第一个步骤,负责将.class文件加载至内存,但又不仅可以从本地 .class 文件加载一个类或接口,也可以从JAR包、WAR包、远程网络资源获取并加载到虚拟机,通过动态代理技术可以在运行时动态生成。比如JSP文件也可以生成对应的Class类。

2、验证

对加载的Class信息首先要做验证,检测文件格式、元数据、字节码等信息是否合法,是否符合虚拟机的最低要求,校验文件是否对虚拟机有害。

文件格式验证:即验证类文件结构
元数据验证:这个是否有父类,父类是否继承了不允许被继承的类等等
字节码验证:对类的方法体进行校验,JDK1.6后只需检查StackMapTable属性中的记录是否合法,JDK1.7后对于主版本号大于50的Class文件,使用类型检查来完成数据流分析
符号引用验证:全限定名是否能找到对应的类,在指定类中是否存在符合方法的字段描述以及简单名称描述的方法,字段。访问性是否正确。验证不成功会抛出java.lang.incompatibleClassChangeError异常的子类。

3、准备

变量所需的内存大小在编译期就已经确定,而类加载的准备阶段是为类变量分配内存并设置类变量的初始值(区别于用户指定值),这些类变量的使用的内存区域是线程共享的方法区(Method Area)。

4、解析

虚拟机将用于标识引用的符号替换为实际指向的引用的地址。符号或符号引用只不过是个标识(描述符),而实际地址才是真正的目的内存位置。

解析动作主要针对:类或接口、字段(类成员变量)、类方法、接口方法等引用进行。

类或接口的解析:判断所要转化成的直接引用是对数组类型,还是对普通的对象类型的引用,从而进行不同的解析。
字段解析:对字段进行解析时,会先在本类中查找是否包含有简单名称和字段描述符都与目标相匹配的字段,如果有,则查找结束;如果没有,则会按照继承关系从上往下递归搜索该类所实现的各个接口和它们的父接口,还没有,则按照继承关系从上往下递归搜索其父类,直至查找结束,查找流程如下图所示:
在这里插入图片描述
最后需要注意:理论上是按照上述顺序进行搜索解析,但在实际应用中,虚拟机的编译器实现可能要比上述规范要求的更严格一些。如果有一个同名字段同时出现在该类的接口和父类中,或同时在自己或父类的接口中出现,编译器可能会拒绝编译。

类方法解析:对类方法的解析与对字段解析的搜索步骤差不多,只是多了判断该方法所处的是类还是接口的步骤,而且对类方法的匹配搜索,是先搜索父类,再搜索接口。
接口方法解析:与类方法解析步骤类似,由于接口不会有父类,因此,只递归向上搜索父接口就行了。

5、初始化

给类的静态变量初始化值,不同于准备阶段,此处是使用用于自定义的值进行赋值。

双亲委派模型

(1)Bootstrap Class Loader:
JDK自带的一款类加载器,用于加载JDK内部的类。Bootstrap类加载器用于加载JDK中$JAVA_HOME/jre/lib下面的那些类,比如rt.jar包里面的类。

(2)Extension Class Loader
主要用于加载JDK扩展包里的类。一般$JAVA_HOME/lib/ext下面的包都是通过这个类加载器加载的,这个包下面的类基本上是以javax开头的。

(3)Application Class Loader
在这里插入图片描述

为什么叫“双亲委派模型”?

这是个很蛋疼的翻译问题,实际上在oracle官方文档上,人家是这样描述的:

The Java platform uses a delegation model for loading classes. The basic idea is that every class loader has a “parent” class loader. When loading a class, a class loader first “delegates” the search for the class to its parent class loader before attempting to find the class itself.
复制代码

java平台通过委派模型去加载类。每个类加载器都有一个父加载器。当需要加载类时,会优先委派当前所在的类的加载器的父加载器去加载这个类。如果父加载器无法加载到这个类时,再尝试在当前所在的类的加载器中加载这个类。

所以,java的类加载机制应该叫做“父委派模型”,不应该叫做“双亲委派机制”,“双亲委派机制”这个名字太具有误导性了。这个命名的人可能男女平等观念比较深。

双亲委派模型的优点

Java的类加载使用双亲委派模式,即一个类加载器在加载类时,先把这个请求委托给自己的父类加载器去执行,如果父类加载器还存在父类加载器,就继续向上委托,直到顶层的启动类加载器。如果父类加载器能够完成类加载,就成功返回,如果父类加载器无法完成加载,那么子加载器才会尝试自己去加载。

这种双亲委派模式的好处,一个可以避免类的重复加载,另外也避免了java的核心API被篡改。

父类加载器已经加载过的类,不用再次加载,而且对于一些系统类,用户自定义的不起作用了,有一定安全保证。

双亲委派的工作机制

“父委派模型”是怎么工作的?
举个例子,当前有个Test.class,需要加载rt.jar中的java.lang.String,那么加载的流程如下图所示,整体的加载流程是向上委托父加载器完成的。

如果整个链路中,父加载器都没有加载这个类,且无法加载这个类时,才会由Test.class所在的加载器去加载某个类(例如希望加载开发人员自定义的类 Test2.class)。
在这里插入图片描述
参考:Java类加载机制:双亲委派机制,还是应该叫做“父委派模型”? blog.csdn.net/u010841296/…

文章分类
代码人生
文章标签