类加载过程

200 阅读6分钟

虚拟机将class文件加载到内存中,并对数据进行校验、转换解析、初始化,最终形成可被虚拟机直接使用的java类型。

类加载时机

生命周期:加载、验证、准备、解析、初始化、使用、卸载。
验证、准备、解析三个部分统称为连接。
若java动态生成,则解析阶段将在初始化动态生成类文件后再执行解析。

avatar

初始化条件:

  1. new、getstatic、putstatic、invokestatic需要进行初始化。
  2. 使用java.lang.reflect包对类进行反射调用。
  3. 初始化一个类,需先触发父类的初始化。
  4. 虚拟机启动时,用户指定一个要执行的主类,虚拟机会先初始化主类。
  5. java.lang.invoke.MethodHandle解析结果REF_getStatic、REF_putStatic、Ref_invokeStatic方法句柄,需要进行初始化。
    在以上5种任何一种情况下都会对类进行初始化。

类加载过程

加载

加载阶段虚拟机主要完成三件事

  1. 通过全限定名获取类二进制字节流。(可能通过类、动态、网络等多方面获取)
  2. 将二进制字节流静态存储结构转化为方法区运行时数据结构。
  3. 在内存中生成一个代表java.lang.Class对象,作为方法区这个类的各种数据访问入口。
    对于数组类不通过类加载器进行加载,而是由Java虚拟机直接创建。但数组类中的元素类型通过类加载器创建。

验证

确保Class文件的字节流中包含的信息符合当前的虚拟机要求,不会造成虚拟机自身安全问题。
验证阶段主要包含4个检验动作:

文件格式验证

是否符合Class文件格式规范,能被当前版本虚拟机处理。

  1. 是否以魔数0xCAFEBABE开头
  2. 主次版本号是否在虚拟机处理范围内
  3. 常量是否由不被支持的常量类型
  4. 指向常量的索引值中是否有指向不存在的常量或不符合类型的常量
    ……………………
    等等
    主要验证输入的字节流能正确地解析并存储于方法区内。

元数据验证

对字节码描述的信息进行语义分析,以保证描述的信息符合Java语言规范要求。

  1. 是否有父类(Object除外)
  2. 父类是否包含不允许被继承的类(final修饰的类)
  3. 是否为抽象类,是否实现父类或接口类中的所有方法。
  4. 类中字段、方法是否合父类矛盾。
    …………
    等等
    以确保不存在不符合Java语言规范的元数据信息。

字节码验证

通过数据流合控制流分析,确定程序语义的合法、符合逻辑。

  1. 保证操作数栈的数据类型与指令代码序列能配合工作。
  2. 保证跳转指令正确。
  3. 保证方法类型转换有效。
    …………
    等等
    若没通过字节码验证,则无法说明其一定安全。

符号引用验证

对类自身意外的信息进行匹配性校验。

  1. 符号引用通过字符串描述的全限定名是否能找到对应的类。
  2. 指定类中是否存在符号方法的字段描述符以及简单名称所描述的方法和字段。
  3. 符号引用中的类、字段、方法的访问性是否可被当前类访问。
    ………………
    等等
    确保解析动作能够正常执行。

准备

为类变量分配内存、设置类初始值,并在方法区中进行内存分配。
其中仅针对类变量(static修饰的变量)进行内存分配,不包含实例变量。实例对象在对象实例化时随着对象一起分配在Java堆中。
基本数据初始值为零值。

avatar
但若在static静态字段变量后加上final生成ConstantValue属性,则在准备阶段时便附上初始化值。

解析

虚拟机将常量池内的符号引用替换为直接引用的过程。
符号引用:以一组符号描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。引用的目标并不一定已经加载到内存中。
直接引用:直接指向目标的指针、相对偏移量是以恶能间接定位到目标的句柄。与内存布局相关,同一符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。若是直接引用则说明该引用的目标已经在内存中。
解析主要对类、接口、字段、类方法、接口方法、接口类型、方法句柄和调用点限制符号引用。

初始化

初始化阶段是执行类构造器()方法的过程。
根据程序猿通过程序指定的主观计划去初始化类变量和其他资源。

avatar

类加载器

用于实现类加载的动作。每个类都需要有类加载器和本身一同确定其在Java虚拟机中的唯一性。都拥有独立的类名称空间。
若两个类源自于同一个Class文件,被同一个虚拟机加载,但只要类加载其不一样,则判定两个类必定不相等。

双亲委派模型

两种不同的类加载器:由C++启动类加载器,为虚拟机的一部分。另外一种由java语言启动其他类加载器,独立于虚拟机外部,继承java.lang.ClassLoad。
java程序使用的三种形式

  1. 启动类加载器:即放在<Java_Home>\lib目录下或有-Xbootclasspath参数所指向路径下,由虚拟机识别的类库加载到虚拟机内存中。
  2. 扩展类加载器:负责加载<JAVA_HOME>\lib\ext目录中锁指定的库。
  3. 应用程序加载器:负责加载用户路径上所指定的类库,可直接使用这个类的加载器。
    应用程序是由该3种类加载器互相配合进行加载的,可加入自定义类加载器。
    一般称为类加载器的双亲委派模型。
    若一个类加载器收到请求后,不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,最终请求都会到启动类加载器中,只有父类反馈自己无法完成加载,在加载器才会尝试自己去加载。

字节码执行引擎

支持虚拟机进行方法调用和方法执行的数据结构,是虚拟机运行时数据区中的虚拟机栈的栈元素。存储方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每个方法从调用到结束对应一个栈帧在虚拟机从入栈到出栈的过程。