『面试必问JVM系列』虚拟机类加载机制

Java虚拟机类加载机制详解
本文详细介绍了Java虚拟机的类加载机制,包括加载、验证、准备、初始化和卸载五个阶段,以及类加载的五种触发初始化的情况。在加载过程中,虚拟机将Class文件的二进制流转化为运行时数据结构,并进行验证,确保符合虚拟机要求。类加载过程涉及符号引用验证,确保解析动作能正常执行。此外,文章还提到了数组类的特殊加载规则和类加载的顺序。

虚拟机类加载机制

相关视频教程参考(来自动力节点):https://www.bilibili.com/video/BV1e64y197rh

相关资料下载:http://www.bjpowernode.com/?csdn

虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、装换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型。

在 Java 语言中,类型的加载、连接和初始化过程都是在程序运行期间完成的。

1、类加载时机

类的生命周期( 7 个阶段)

其中加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的。解析阶段可以在初始化之后再开始(运行时绑定或动态绑定或晚期绑定)。

以下五种情况必须对类进行初始化(而加载、验证、准备自然需要在此之前完成):

  • 遇到 new、getstatic、putstatic 或 invokestatic 这 4 条字节码指令时没初始化触发初始化。使用场景:使用 new 关键字实例化对象、读取一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)、调用一个类的静态方法。
  • 使用 java.lang.reflect 包的方法对类进行反射调用的时候。
  • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需先触发其父类的初始化。
  • 当虚拟机启动时,用户需指定一个要加载的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类。
  • 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需先触发其初始化。

2、类的加载过程

2.1 加载

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

数组类的特殊性:数组类本身不通过类加载器创建,它是由 Java 虚拟机直接创建的。但数组类与类加载器仍然有很密切的关系,因为数组类的元素类型最终是要靠类加载器去创建的,数组创建过程如下:

  • 如果数组的组件类型是引用类型,那就递归采用类加载加载。
  • 如果数组的组件类型不是引用类型,Java 虚拟机会把数组标记为引导类加载器关联。
  • 数组类的可见性与他的组件类型的可见性一致,如果组件类型不是引用类型,那数组类的可见性将默认为 public。

内存中实例的 java.lang.Class 对象存在方法区中。作为程序访问方法区中这些类型数据的外部接口。

加载阶段与连接阶段的部分内容是交叉进行的,但是开始时间保持先后顺序。

2.2 验证

是连接的第一步,确保 Class 文件的字节流中包含的信息符合当前虚拟机要求。

文件格式验证

  • 是否以魔数 0xCAFEBABE 开头
  • 主、次版本号是否在当前虚拟机处理范围之内
  • 常量池的常量是否有不被支持常量的类型(检查常量 tag 标志)
  • 指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量
  • CONSTANT_Utf8_info 型的常量中是否有不符合 UTF8 编码的数据
  • Class 文件中各个部分集文件本身是否有被删除的附加的其他信息
  • ……

只有通过这个阶段的验证后,字节流才会进入内存的方法区进行存储,所以后面 3 个验证阶段全部是基于方法区的存储结构进行的,不再直接操作字节流。

元数据验证

  • 这个类是否有父类(除 java.lang.Object 之外)
  • 这个类的父类是否继承了不允许被继承的类(final 修饰的类)
  • 如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法
  • 类中的字段、方法是否与父类产生矛盾(覆盖父类 final 字段、出现不符合规范的重载)

这一阶段主要是对类的元数据信息进行语义校验,保证不存在不符合 Java 语言规范的元数据信息。

字节码验证

  • 保证任意时刻操作数栈的数据类型与指令代码序列都鞥配合工作(不会出现按照 long 类型读一个 int 型数据)
  • 保证跳转指令不会跳转到方法体以外的字节码指令上
  • 保证方法体中的类型转换是有效的(子类对象赋值给父类数据类型是安全的,反过来不合法的)
  • ……

这是整个验证过程中最复杂的一个阶段,主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。这个阶段对类的方法体进行校验分析,保证校验类的方法在运行时不会做出危害虚拟机安全的事件。

符号引用验证

  • 符号引用中通过字符创描述的全限定名是否能找到对应的类
  • 在指定类中是否存在符方法的字段描述符以及简单名称所描述的方法和字段
  • 符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问
  • ……

最后一个阶段的校验发生在迅疾将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段——解析阶段中发生。符号引用验证可以看做是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验,还有以上提及的内容。

符号引用的目的是确保解析动作能正常执行,如果无法通过符号引用验证将抛出一个 java.lang.IncompatibleClass.ChangeError 异常的子类。如 java.lang.IllegalAccessError、java.lang.NoSuchFieldError、java.lang.NoSuchMethodError 等。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值