类型装载、连接与初始化
Java虚拟机通过装载、连接和初始化一个Java类型,使该类型可以被正在运行的Java程序所使用。其中装载就是把二进制形式的Java class文件读入Java虚拟机中去;连接把读入虚拟机的二进制形式的Java class文件合并到虚拟机的运行时的状态中去。连接可以分为三个子步骤,验证,准备和初始化。验证步骤确保了Java类型数据格式正确并且适于Java虚拟机使用。准备为该类型分配内存(如为类变量分配内存)。解析负责将常量池中的符合引用转换为直接引用,虚拟机的实现可以推迟解析这一步骤。即当运行中的程序真正使用某个符号的时候再去解析它。(符号引用变为直接引用)。初始化为类变量赋以适当的初始值。
下面就这5个步骤进行详细的解析:
装载
装载阶段由三个动作完成
- 通过该类型的完全限定名,产生一个代表该类型的二进制数据流。
该二进制数据流可以遵循class文件格式,也可以遵守其他数据格式。所有虚拟机必须能识别Java class文件格式,但是个别的也可以识别其他二进制格式。
Java虚拟机并未规定该二进制数据流如何产生,因此下面列出了可能产生二进制数据的方式:
从本地文件系统装载class文件;
从网络下载一个class文件;
从ZIP,JAR,CAB中提取class文件;
从专有数据库中提取class文件;
将Java源文件编译为class文件;
使用上述方法,但是产生非class文件格式的二进制文件格式 - 解析这个二进制数据流为方法区的内部数据结构。
- 创建一个表示该类型的java.lang.class类的实例。
装载的最终目的就是创建java.lang.class的实例对象,解析二进制数据流为方法区的内部数据结构,并在堆上面建立一个Class对象。Class对象是Java程序与内部数据结构之间的接口。
Java类型要么被启动类装载器装载,要么被用户自定义的类加载器加载。
类加载器并不是在某个类型首次主动使用的时候加载它。可以提前预判,预加载这个类型。如果预加载的时候出现问题,不会立即报错。直到程序主动使用该类才报错。也就是说程序已知不主动使用该类类型,类加载器就一直不报错。
装载时会做两项检查,虽然在验证阶段之前进行,但逻辑上属于验证阶段。
- 验证class文件格式是否正确,如魔数,每个部分在正确位置,正确的长度,文件的长度不是太长或太短等。 保证正式解析二进制class文件时候,不会造成虚拟机的崩溃。
- 确保每个除Object之外每个类都有一个超类,加载某个类的时候,确保该类的所有超类必须加载。
验证
类型被加载后,虚拟机开始连接,连接第一步是验证,即确认它是否符合Java语言的语义,并且不会危及虚拟机的完整性。
验证的时机和方式,不同虚拟机有着不同的实现。虚拟机规范只是规定了虚拟机在验证过程中可以抛出的异常以及在何种情况下必须抛出它们。
验证阶段往往会验证如下内容:
- 检查final的类不拥有子类
- 检查final方法不能被覆盖
- 确保类型和超类型之间没有不兼容的方法申明(比如两个方法签名完全一样,但返回类型不同)
- 检查所有的常量池入口相互之间保持一致(比如CONSTANT_String_info入口的string_index项目必须是一个CONSTANT_Utf8_info入口的索引)
- 检查常量池中所有的特殊字符串(类名,字段名,方法名,字段描述符和方法描述符)是否符合格式
- 检查字节码的完整性
上述任务最复杂的是最后一个,检查字节码的完整性。不能因为挑战指令超出了方法末尾导致了虚拟机崩溃。虚拟机必须在验证阶段检查出这样的字节码是非法的,从而抛出一个错误。
不过虚拟机规范并未规定字节码验证必须在验证阶段执行,也可以在执行每条语句的时候单独进行验证。Java虚拟机指令集当初设定目标是字节码流一次性使用数据流分析器进行验证。不过运行时候进行验证,会使得Java程序运行速度提高。当使用数据流分析器进行字节码验证时候,可能为了确保符合Java语言的语义而装载其他的类。比如加载了Float类,必须还加载其父类Number保证其不是一个final类。
验证阶段之后可能还会进行一项检查,那就是符号引用的验证。虚拟机在将符号引用替换为直接引用,需要检查该元素的存在性以及访问的权限。逻辑上属于验证,但往往在解析阶段发生。
准备
在准备阶段,Java虚拟机为类变量分配内存,并设置默认的初始值。在正式初始化阶段之前,类变量都没有被正式初始化为真正的初始值。
准备阶段,干的另外一件事就是可能为一些数据结构分配内存,进而提高程序运行的性能。比如方法表的建立,它包含了指向类中每一个方法包括从超类继承方法的指针。从而可以在执行继承方法时候不需要搜索超类。
解析
解析过程就是在类型的常量池中寻找类、接口、自动和方法的符合引用,把这些符号引用变成直接引用的过程。还记得逻辑上属于验证的符合引用的验证么?解析什么时候执行是虚拟机自己决定的,可以在初始化阶段后面执行。
初始化
初始化阶段为类变量赋予正确的初始值。在准备阶段,类变量被赋予了默认值,在这一阶段,类变量被赋予正确的初始值,即程序员希望这个类变量具备的起始值。
具体的初始化过程参见下一篇博客《浅谈类加载的初始化阶段》
原文地址:https://www.cnblogs.com/wood-lin/p/9019419.html