天天看点

Java类的加载过程

概述

在Java语言中,类型的加载、连接和初始化过程都是在程序运行期间完成的,这种策略虽然会令类加载时稍微增加一些性能开销,但是会为Java应用程序提供高度的灵活性,Java里天生可以动态扩展的语言特性就是依赖运行期动态加载和动态链接这个特点实现的。例如,如果编写一个面向接口的应用程序,可以等到运行时再指定其实际的实现类:用户可以通过Java预定义和自定义类加载器,让一个本地的应用程序可以在运行时从网络或其他地方加载一个二进制流作为程序代码的一部分,这种组装应用程序的方式已经广发应用于Java程序之中。

类加载的时机

类从被加载到虚拟机内存中开始,到卸载出内存为止,他的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。其中验证、准备、解析3个部分统称为连接(Linking)。这7个阶段的发生顺序如图所示:

Java类的加载过程

加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班的开始,而解析阶段则不一定:他在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)。注意,这里写的是按部就班的开始,而不是按部就班的进行或完成,强调这点是因为这些阶段通常都是互相交叉的混合式进行的,通常会在一个阶段执行的过程中调用,激活另外一个阶段。

那什么时候开始类加载过程的第一个阶段?Java虚拟机并没有进行强制约束,这点可以交给虚拟机的具体实现来自由把握。但是对于初始化阶段,虚拟机规范则是严格规定了有且只有5种情况必须立即对类进行初始化(而加载、验证、准备自然需要在此之前开始):

  • 遇到new、getstatic、putstatic或invokestatic这4调字节码指令时,如果累没有进行过初始化,则需要先出法其初始化。生成这4条指令的最常见场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段的时候,以及调用一个类的静态方法的时候。
  • 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化则需要先触发其初始化。
  • 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
  • 当虚拟机启动时,用户需要指定一个需要执行的主类,虚拟机会先初始化这个主类。
  • 当使用动态语言的时候,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的的类没有进行过初始化,则需要先触发其初始化。

除此之外,所有引用类的方式都不会触发初始化,成为被动引用。

类加载的过程

加载

在加载阶段,虚拟机需要完成以下3件事情:

  • 通过一个类的全限定名来获取定义此类的二进制字节流。
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

验证

验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息渡河当前虚拟机的药企,并且不会危害虚拟机自身的安全。

验证阶段大致上会完成下面4个阶段的检验动作:

  • 文件格式验证:是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。
  • 元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。
  • 字节码验证:整个验证过程中最复杂的阶段,主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
  • 符号引用验证:最后一个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三个阶段–解析阶段中发生。符号引用验证可以看做是对类自身以外的信息进行匹配性校验。

准备

准备阶段是正式为类的变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这个阶段中有两个容易产生混淆的概念需要强调一下,首先,这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。

解析

解析阶段是hi虚拟机将常量池内的符号引用替换为直接引用的过程,那直接引用于符号引用又有什么关联呢?

  • 符号引用:以一组符号来描述所引用的目标,符号可以使任何形式的字面量,只要使用时能无歧义的定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。
  • 直接引用:直接引用可以使直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在内存中存在。

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

初始化

类初始化阶段是类架子啊过程的最后一步 ,前面的类加载过程中,出了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。

在准备阶段,变量已经赋值过一次系统要求的初始值(零值),而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始化阶段是执行类构造器()方法的过程。