什么是类加载?
前面的文章已经说过,我们手动敲代码,写出来的文件是.java文件。 虚拟机编译之后,可识别的文件是.class文件。
但是在真正运行的时候,在内存中进行各种流转,通过程序来进行执行的时候,应该 是遗传二进制字节流这种 类型的文件。
所以,我们可以认为加载过程是“将class文件加载为程序运行中可操作的对象”
本章呢,就是先分析一下整个类加载过程都有哪些动作,下一章再剖析一下类加载器以及双亲委派机制
什么时候进行类加载?
一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载
(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化
(Initialization)、使用(Using)和卸载(Unloading)七个阶段,其中验证、准备、解析三个部分统称为连接(Linking)。这七个阶段的发生顺序如图:
加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的。但是解析阶段有可能是在初始化阶段之后开始的,是为了Java语言运行时绑定特性。
关于具体什么情况下开始“加载”,《Java虚拟机规范》中并没有强制约束。
但是!对于“初始化”阶段,《Java虚拟机规范》严格规定了有且只有六种情况必须对类进行“初始化”。
而“加载”,“验证”,“准备”都要在初始化之前,所以也就相当于变相的规定了“加载”的开始。也就是下面六种情况
- 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类型没有进行过初始化,则需要先触发其初始化阶段。
- 使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需要先触发其初始化。
- 当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
- 当使用JDK 7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStaticREF_invokeStatic、REF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。
- 当一个接口中定义了JDK 8新加入的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
类加载过程
加载阶段
“加载”(Loading)阶段是整个“类加载”(Class Loading)过程中的一个阶段,希望读者没有混淆
这两个看起来很相似的名词。在加载阶段,Java虚拟机需要完成以下三件事情:
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
加载阶段结束后,Java虚拟机外部的二进制字节流就按照虚拟机所设定的格式存储在方法区之中了,方法区中的数据存储格式完全由虚拟机实现自行定义,《Java虚拟机规范》未规定此区域的具体数据结构。类型数据妥善安置在方法区之后,会在Java堆内存中实例化一个java.lang.Class类的对象,这个对象将作为程序访问方法区中的类型数据的外部接口。
验证阶段
验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。
- 文件格式验证(校验文件是否规范,class文件各个部分是否完整等等)
- 元数据验证(是否存在父类,是否为抽象类,是否与父类产生冲突矛盾,主要进行语义校验)
- 字节码验证(一个是校验代码是否会篡改计算机底层逻辑,是否安全;另一方面是校验数据类型等等 信息)
- 符号引用验证(主要确保各个名称是否能找到对应类,类中的字段方法等信息是否可以正常使用)
准备阶段
主要为类中定义的变量(静态变量,static修饰的变量),分配内存并且设置类变量初始阶段。
要注意,这里只有类变量,不包括实例变量。实例变量会随着对象的实例化一起分配在Java堆中。
解析
笼统来说,是Java虚拟机将常量池中的“符号引用”替换为“直接引用”的过程。
这块内容我们后面要单开一章来进行讲解,这里就简单说一下两个的关联。
- 符号引用:以一组符号来描述所引用的目标,与内存布局无关,只要可以指代目标就可以。
- 直接引用:直接指向目标的一个指针,与内存布局相关,必须是目标已经在虚拟机中存在的。
并且解析动作还有不同的类型,用来处理不同的数据。
主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符这7类符号引用进行
初始化
类的初始化阶段是类加载过程的最后一个步骤,之前介绍的几个类加载的动作里,除了在加载阶段用户应用程序可以通过自定义类加载器的方式局部参与外,其余动作都完全由Java虚拟机来主导控制。直到初始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序。
可以理解为,初始化阶段就是执行类的构造器方法的过程