Java虚拟机学习之类加载机制将

时间:2022-12-29 13:10:49

一、什么是类的加载机制

1、虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。
2、类的加载、连接和初始化过程都是在程序运行期间完成的,这种策略虽然会令类加载时稍微增加一些性能开销,但是会为Java应用程序提供高灵活性。
3、Java里天生可以动态扩展的语言特性就是依赖云溪区动态加载和动态链接这个特点实现的。例如编写一个面向接口的应用程序,可以等到运行时再指定实际的类。
4、用户可以通过Java预定义的和自定义的类加载器,让一个本地的应用程序可以在运行时从网络或其他地方加载一个二进制流作为程序代码的一部分。

二、类加载的时机

Java虚拟机学习之类加载机制将
(类的生命周期    图片来自网络)

加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定。

五种情况必须立即对类进行“初始化”(主动引用)
1、遇到new、getstatic、putstatic或invokestatic这4条字节码指令时。分别是创建对象、访问类属性、调用静态方法时。
2、使用java.lang.reflect包的方法对类进行反射调用的时候。
3、当初始化一个类时,如果发现其父类还没有进行过初始化,则先触发其父类的初始化。
4、当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先初始化这个主类。
5、如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄。

以下几种情况为被动引用,不会出发初始化。
1、通过子类引用父类的静态字段,不会导致子类初始化。只会触发父类的初始化。
如:System.out.println(SubClass.value);//value是父类的静态属性。

2、通过数组定义来引用类,不会出发此类的初始化。这段代码出发了一个虚拟机自动生成的“[Lcom.xxx.SuperClass”的类,这个类直接继承自Object,由字节码指令newarray触发。
如:SuperClass[] sca = new SuperClass[10];

3、调用某个未初始化类的常量,常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义的常量类,因此不会出发常量的类的初始化。在编译阶段通过常量传播优化,已经将此常量的值存储到了主函数类的常量池中,以后对常量的引用都转化为对main函数所属类的常量引用了。

接口与类的初始化异同
接口也有初始化过程,接口中不能使用“static{}”语句块,但编译器仍然会为接口生成“<clinit>()”类构造器用于初始化接口中所定义的成员变量。
一个类在初始化时要求其父类全部已经被初始化过了,而一个接口在初始化时,并不要求其父接口全部都完成了初始化工作,只有在真正使用到父接口时才会初始化。


三、类加载的过程

1、加载
“加载”是“类加载”过程的一个阶段,不要搞混。
具体流程:
(1)通过一个类的全限定名来获取定义此类的二进制字节流。
(2)将这个字节流的静态存储结构转化为方法区的运行时数据结构。
(3)将内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

数组类与非数组类加载的区别:
非数组类可以使用系统提供的引导类加载器,也可以由用户自定义的类加载器完成。
数组类本身不通过类加载器创建,它时有Java虚拟机直接创建的,数组类的元素类型最终是要靠类加载器去创建。

加载阶段与连接阶段的部分内容(如一部分字节码文件格式验证动作)是交叉进行的。

2、验证
验证是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
如果验证到输入的字节流不符合Class文件格式的约束,抛出java.lang.VerifyError异常或其子类异常。

(1)文件格式验证
主要目的:验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。
只有通过了文件格式验证,字节流才会进入内存的方法区进行存储。

(2)元数据验证
主要目的:对字节码描述的信息进行语义分析,保证不存在不符合Java语言规范的元数据信息。

(3)字节码验证
主要目的:通过数据流的控制和流分析,确定程序语义是合法的、符合逻辑的。

3、准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的的内存都将在方法区中进行分配。
假设一个类变量的定义为:
public static int value = 123;
那么value变量在准备阶段过后的初始值是0而不是123,因为这时候未开始执行任何java方法,而把value复制为123的putstatic指令是程序被编译后,存放于<clinit>()方法之中,所以在初始化阶段才会执行。
准备阶段是赋值的初始值通常情况下是零值。
特殊情况:
字段属性表中存在ConstantValue属性,那么在准备阶段变量value就会被初始化为ConstantValue属性所指定的值。
即加final变成常量,
如public static final int value = 123;

4、解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

符号引用:以一组符号来描述所引用的目标,可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可。

直接引用:直接引用可以使直接指向目标的指针、相对偏移量或是能间接定位到目标的句柄。

(1)类或接口的解析
假设当前所处代码为D类,如果要把一个从未解析过的符号引用N解析为一个类或接口C的直接引用,则要进行以下操作。

如果加载非数组类型:把全限定名传递给D的类加载器去加载C类,可能会出发其相关类的加载动作,例如这个类的父类或实现的接口。

如果C是数组类型,则会按照低一点的规则加载数组元素类型,接着又虚拟机生成一个代表此数组维度和元素的数组对象。

(2)字段解析

假设这个字段所属的类或接口为C。

如果C本身就包含了这个字段,则返回这个字段的直接引用。

否则,如果在C中实现了接口,将会按照继承关系从下往上递归搜索各个接口和它的父接口,如果接口中包含了简单名称和字段描述符都与目标相匹配的字段,则返回该字段的直接引用。

否则,从下往上递归搜索父类,查找到则返回。

否则抛出NoSuchFieldError异常。

注意:如果有一个同名字段同时出现在C的接口和父类中,或者同时在多个接口中出现,编译不通过。

(3)类方法解析


(4)接口方法解析

5、初始化

类初始化时类加载的最后一步,到了初始化阶段,才真正开始执行类中的Java代码。
在准备阶段,变量已经赋值过初始值。

(1)<clinit>()方法时由编译器自动收集类中的所有变量的赋值动作和静态语句块中的语句合并而成。编译器收集的顺序室友语句在源文件中出现的顺序所决定的。
注意:静态语句块只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,不能访问。

(2)<clinit>()方法不需要显示地调用父类构造器,虚拟机会保证子类的<clinit>()方法执行之前,父类<clinit>()方法以及执行完毕。

(3)由于父类的<clinit>()先执行,意味着父类中定义的静态语句块要优于子类放入变量赋值操作。

(4)<clinit>方法对于类或接口来说并不是必需的,如果一个类中没有静态语句块和静态变量赋值操作,那么可以不生成<clinit>方法。

(5)接口中不能使用静态语句块(static{})。

(6)虚拟机保证一个类的<clinit>方法在多线程环境中被正确的枷锁、同步,如果多个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待。

四、类加载器

1、类加载器的作用
作用:实现类的加载动作。

比较两个类是否相等:只有两个类是由同一个类加载器加载的前提下,才能判定这两个类相等,否则,即使两个类源于同一个类加载文件,只要加载他们的虚拟机不同,那么这两个类就不相同。

使用instanceof关键字做对象所属关系判定要注意到类加载器的影响。

2、类加载器的分类
从虚拟机角度讲,只存在两种不同的类加载器:
一种是启动类加载器(Bootstrap),使用C++实现,是虚拟机自身的一部分。
一种是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全部继承自抽象类ClassLoader。

启动类加载器:加载<JAVA_HOME>\lib目录中的,或被-Xbootclasspath参数所指定的路径中的。

扩展类加载器:加载<JAVA_HOME>\lib\ext中的,开发者可以直接使用。

应用程序类加载器:加载用户类路径(ClassPath)上所指定的类库。

3、双亲委派模型
双亲委派模型要求除了顶层的启动类加载器外,其余类加载器都应当有自己的父类加载器。
这里类加载器的父子关系不是继承,而是使用组合关系。

工作过程:一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求为派给父类加载器去完成,每一个层次的类加载器都如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时(自己的搜索范围未找到该类),子加载器才会尝试自己去加载。

好处:这种带有优先级的层级关系,保证各种类加载器在各种类加载器环境中都是同一个类。
Java虚拟机学习之类加载机制将