Java虚拟机--虚拟机类加载机制

2018-06-20  本文已影响87人  sunhaiyu

虚拟机类加载机制

虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

类的生命周期如下:

加载 --> 验证 --> 准备 --> 解析 --> 初始化 --> 使用 --> 卸载

其中验证、准备、解析三个阶段属于连接过程。解析可以发生在初始化之后,这是因为Java支持动态绑定,或者说运行时绑定。

发生下面5种情况,必须立即对类进行初始化:

以上5种情况,称为对一个类的主动引用。其余情况都被称为被动引用。来看几中被动引用的情况;

接口也有初始化过程,和类的初始化的区别在于:接口在初始化时,不要求求父接口必须先初始化完毕,而是等用到父接口时,在初始化父接口。

加载

加载阶段要完成以下三件事:

非数组类的加载可以使用系统提供的引导类加载器,也可以使用自定义的类加载器去完成。

数组类本身不通过类加载器创建,有Java虚拟机直接创建,但是数组类的元素类型(如Object[]中的Object,Object[][]中的Object)最终还是由类加载器创建。

元素类型分两种,引用类型和基本类型。

验证

验证阶段包括以下4个检验:

准备

正式为类变量分配内存并设置类变量(被static修饰)的初始值,初始值一般为“零值”,比如对于int型来说是0,对于boolean来说是false。下面的例子:

public static int val = 9;

准备阶段后初始值为0而不是9,val被赋值为9是在初始化阶段

但如果是

public final static int val = 9;

因为有final修饰,所有在准备阶段就会将val赋值为9。

解析

虚拟机将常量池内的符号引用替换为直接引用的过程。

解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄、和调用点限定符7类符号引用进行。

类或接口的解析

如果当前类是D,要把一个从未解析过的符号引用N解析成一个类或者接口的直接引用,需要如下三个步骤:

字段解析

首先会解析字段所属的类或接口的符号引用。如果解析成功,将字段所属的类或接口用C表示。之后对C进行后续字段的搜索

查找过程中成功返回引用后,将会对这个字段进行权限验证,如果发现不具备对字段的访问权限,将抛出异常。

类方法解析

首先会解析方法所属的类或接口的符号引用。如果解析成功,将字段所属的类用C表示。之后对C

如果查找成功并成功返回了直接引用,会对这个方法进行权限验证,如果发现不具备对此方法的访问权限,抛出异常。

接口方法解析

首先会解析方法所属的类或接口的符号引用。如果解析成功,将字段所属的接口用C表示。之后对C

因为接口中的方法都是public,所以不存在像上面一样的访问权限问题。

初始化

初始化阶段才真正开始执行类中定义的Java程序代码。初始化阶段是执行类构造器<clinit>()方法的过程(不是实例构造器<init>())。

public static int val = 9;

在准备阶段val是0,而在初始化阶段val才被赋值为9。

类加载器

类加载器:通过一个类的全限定名来获取描述此类的二进制字节流。

任何一个类,都需要由加载它的类加载器和类本身一同确定在Java虚拟机的唯一性,每一个类加载器都拥有一个独立的类名称空间,通俗地说:比较两个类是否“相等”,只有在这两个类是由同一个类加载器的前提下才有意义。即使两个类来自同一个Class文件,且被同一个Java虚拟机加载,只要它们不是被同一个类加载器加载的,这两个类就不相等。

双亲委派模型

对JVM来讲,只存在两种不同的类加载器:

再细分的话

应用程序都是由这三种类加载器互相配合进行加载的,这些类加载器的关系一般是以双亲委派模型呈现。双亲委派模型:除了顶层的启动类加载器外,其余类加载器都应当有自己的父类加载器。这里的父子关系一般是以组合关系而不是继承。

20160506184936657

双亲委派模型的工作过程:一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是委派给父类去完成,所以最终的请求被传送到了顶层的启动类加载器中;只有当父类反馈自己无法完成这个加载请求时,子类才会尝试自己去加载。

双亲委派模型的优点:Java类随着它的类加载器一起具备了一种带有优先级的层次关系。比如java.lang.Object类,存放在rt.jar中,无论哪一个类加载器都要加载这个类,最后都委派给了顶端的启动类加载器进行加载了,所以Object类在程序的各种加载器环境种都是同一个类

双亲委派模型的实现:先检查是否被加载过,若没有就调用父加载器的loadClass()方法,若父加载器为空就使用启动类加载器作为父加载器,如果父加载器加载失败,抛出异常后,在调用自己的findClass()进行加载。


by @sunhaiyu

2018.6.11

上一篇 下一篇

猜你喜欢

热点阅读