类的加载过程分为5个步骤:加载、验证、准备、解析、初始化 其中的验证、准备、解析阶段又统称为连接,如下图所示。 在这5个阶段中,加载、验证、准备、初始化这4个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定,为了支持java语言的运行时绑定,它在某些情况下可以在初始化阶段之后再开始。 这里之所以说按部就班地开始,而不是按部就班地“进行”或“完成”,是因为这些阶段通常可以交叉混合进行,如在加载阶段执行过程中,也会同时执行验证阶段。 什么时候要开始一个类的类加载? 什么情况下需要开始类加载过程的第一步“加载”?Java虚拟机并没有进行强制约束,这点可以由虚拟机自行实现。 但是对于初始化阶段,虚拟机规范则进行了严格规定,有且只有在以下7种情况下,必须立即对类进行初始化(而加载、验证、准备自然需要在此之前开始):
类加载过程详解加载 在加载阶段,虚拟机需要完成以下3件事情:
验证阶段大致分4个阶段:
此阶段的验证是基于二进制字节流进行的,只有通过了这个阶段的验证后,字节流才会进入内存的方法区中进行存储,所以后面3个阶段全部是基于方法区的存储结构进行的,不再直接操作字节流。(从这里也可以看出,验证阶段是和加载阶段一起进行的,只有当验证阶段完成后,加载阶段的第二个步骤将字节流转储为方法区的运行时数据结构才能完成) 准备准备阶段是正式为类变量分配内存并设置类变量初始值。 需要注意的是,类似于: “public static int value=123” 这种定义,在准备阶段过后的初始值是0而不是123,把value赋值为123需要等到初始化阶段再执行。 但是如果是: “public static final int value=123” 这种定义,编译时javac会给value生成ConstantValue属性,这种情况下在准备阶段虚拟机就会根据ConstantValue将value赋值为123。 解析 解析阶段是虚拟机将常量池内的符号引用转化为直接引用的过程。
需要注意的有两点:
在类加载的第一个阶段--“加载”阶段,第一个动作是:“通过一个类的全限定名来获取此类的二进制字节流”,我们把实现这个动作的代码模块称为“类加载器”。 类加载器最初是为了满足Java Applet的需求而开发的,虽然目前Java Applet基本已经“死掉”,但是类加载器却在类层次划分、OSGi、热部署、代码加密等领域大放异彩,成为Java体系中的一块重要基石。
必须了解的一个概念是: 对于一个类,这个类本身和加载它的类加载器一同确立其在Java虚拟机的唯一性。 也就是说,不同类加载器加载同一个Class文件所产生的两个类是不相等的,体现在Class对象的equals方法、instanceof关键字的判定等。 类加载器分类
如图:
图中的类加载器之间的层次关系,称为类加载器的双亲委派模型。 双亲委派模型要求除了顶层的启动类加载器之外,其他的加载器都要有自己的父加载器。这里类加载器之间的父子关系不以继承而是以组合方式来实现。 双亲委派模型的工作过程是:每一个类加载器收到类加载请求,都会首先将请求委派到其父加载器去完成,只有当父加载器无法完成加载,子加载器才会尝试自己去加载。
双亲委派模型的好处是Java类随着它的加载器一起具备了优先级的层级关系。如java.lang.Object,它存在于rt.jar,无论哪个类加载器要加载这个类,最终都要委派给顶层的启动加载器,因此Object在程序的各类加载器环境中都是同一个类。即使用户自己定义一个java.lang.Object类,也无法被加载。 |
|