深入理解Java虚拟机——类加载过程

x33g5p2x  于2022-03-21 转载在 Java  
字(2.4k)|赞(0)|评价(0)|浏览(297)

一、加载阶段

1.1、加载的概述

  • “加载”是“类加载”(Class Loading)过程的一个阶段,在加载阶段,虚拟机需要完成以下3件事情:
    (1)、通过一各类的全限定名来获取定义此类的二进制字节流;
    (2)、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
    (3)、在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

1.2、加载的加载源

  • 从ZIP包中读取。
  • 从网络中获取。 最典型的应用就是Applet。
  • 运行时计算生成。 这种场景使用最多的就是动态代理技术,在java.lang.reflect.Proxy中,就是用了ProxyGenerator.generateProxyClass来为特定接口生成形式为“$Proxy”的代理类的二进制字节流。
  • 由其他文件生成。 最典型的应用就是JSP。
  • 从数据库中读取。

二、验证阶段

2.1、验证的目的

  • 为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

2.2、验证阶段完成的4个阶段检验动作

2.2.1、文件格式验证
  • 第一阶段要验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。这一阶段可能包含下面这些验证点。
    (1)、是否以魔数0xCAFEEBABE开头;
    (2)、主、次版本号是否在当前虚拟机处理范围之内;
    (3)、常量池的常量中是否有不被支持的常量类型;
    (4)、指向常量的各种索引中是否有指向不存在的常量或不符合类型的常量;
    (5)、Class文件中各个部分及文件本身是否有被删除的或附加的其他信息等等。
2.2.2、元数据验证
  • 第二阶段是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。这个阶段可能包含下面这些验证点。
    (1)、这个类是否有父类(除了java.lang.Object之外,所有的类都应当有父类);
    (2)、这个类的父类是否继承了不允许被继承的类(被final修饰的类);
    (3)、如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法;
    (4)、类中的字段、方法、是否与父类产生矛盾(例如覆盖了父类的final字段,或者出现不符合规则的方法重载,例如方法参数都一致,但返回值类型却不同等等)。
2.2.3、字节码验证
  • 第三阶段的是整个验证过程中最复杂的一个阶段,主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。这个阶段可能包含下面这些验证点。
    (1)、保证任意时刻操作数栈的数据类型与指令码序列都能配合工作;
    (2)、保证跳转指令不会跳转到方法体以外的字节码指令上;
    (3)、保证方法体中的类型转换是有效的等等。
2.2.4、符号引用验证
  • 最后一个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段——解析阶段中发生。通常需要校验下列内容:
    (1)、符号引用中通过字符串描述的全限定名是否能找到对应的类;
    (2)、在指定类中是否存在符合方法的字段描述以及简单名称所描述的方法和字段;
    (3)、符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问等等。

三、准备阶段

3.1、准备阶段的概述

  • 准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。
  • 这时候进行内存分配的仅包含类变量(被static修饰的变量),而不包含实例变量,实例变量将会在对象实例化时随着对象一起分配Java堆中。
  • 这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量的定义为:
    public static int value = 123; 那么变量value在准备阶段过后的初始值为0而不是123。
  • 假设一个类变量的定义为: public static final int value = 123; 编译时javac将会为value生成ConstantValue属性,在准备阶段虚拟机就回根据ConstantValue的设置将fvalue赋值为123。

四、解析阶段

4.1、解析阶段的概述

  • 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

4.2、符号引用、直接引用的概述

  • 符号引用
    符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量。
  • 直接引用
    直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。

4.3、解析动作

  • 类或接口的解析;
  • 字段解析;
  • 类方法解析;
  • 接口方法解析;

五、初始化阶段

5.1、初始化阶段的概述

  • 类初始化阶段是类加载过程的最后一步,到了初始化阶段,才真正开始执行类中定义的Java程序代码。

5.2、< clinit> 方法

  • 初始化阶段是执行类构造器< clinit> ()方法的过程。
  • < clinit> ()方法是由编译器自动收集类中所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的。
  • < clinit> ()方法与类的构函数(或者说实例构造器< init>()方法)不同,它不需要显式地调用父类构造器,虚拟机会保证在子类的 < clinit> ()方法执行之前,父类的 < clinit> ()方法已经执行完毕。
  • 由于父类的 < clinit> ()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。
  • < clinit> ()方法对于类或者接口来说并不是必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,name编译器可以不为这个类生成 < clinit> ()方法。
  • 接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因为接口与类一样都会生成 < clinit> ()方法。
  • 虚拟机会保证一个类的 < clinit> ()方法在多线程的环境中被正确的加锁、同步。

相关文章