一文看懂JVM类加载过程

JVM类加载机制:

类加载机制:JVM把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称为类加载机制。过程主要分为加载,验证,准备,解析,初始化这五个步骤。

在这里插入图片描述

《Java虚拟机规范》严格规定有且只有六种情况下必须初始化,而加载自然需要在初始化之前:

  1. 遇到new(用于创建一个对象实例)、getstatic(用于读取一个类的静态字段的值),putstatic(用于将操作数栈顶的值赋给一个类的静态字段)、invokestatic(用于调用一个类的静态方法)这四条字节码指令会进行初始化。
  2. 使用java.lang.reflect包的方法对类型进行反射调用。
  3. 当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
  4. JVM启动时,包含main方法的类会被加载。
  5. 当Java虚拟机动态扩展支持其他语言时,如果该语言编写的代码使用了Java类,那么这些Java类也会被初始化。
  6. 当一个接口定义了JDK8新加入的默认方法,如果有类实现了这个接口则进行初始化。

加载:

加载通过一个类的全限定名获取类的二进制流,将字节流的的静态储存转化为方法区的运行时数据结构。

下面是类文件的获取方式:

  1. 文件系统:这是最常见的方式,JVM可以直接从文件系统中的.class文件加载类。例如,当运行一个Java应用程序时,JVM会使用应用程序类加载器(Application ClassLoader)从文件系统中加载主类及其依赖的类。
  2. 网络:JVM可以从网络上的远程服务器加载类。这种方式通常涉及到自定义类加载器,它能够从网络上下载字节码文件并加载类。
  3. JAR/ZIP文件:JVM可以从JAR(Java ARchive)或ZIP文件中加载类。这些文件通常包含了多个.class文件和其他资源,JVM可以使用内置的类加载器或自定义类加载器来读取这些文件。
  4. 数据库:在某些情况下,类的字节码可能存储在数据库中。JVM可以使用自定义类加载器从数据库中读取字节码并加载类。
  5. 其他介质:理论上,只要能够将类的字节码以二进制形式读取,JVM就可以从任何介质加载类,包括内存、自定义文件系统、甚至是动态生成的字节码

验证:

在Java虚拟机(JVM)的类加载过程中,验证阶段(Verification)是非常关键的一个环节。这个阶段的目的是确保加载的类的字节码是合法的、符合JVM规范的,并且不会危害JVM的安全。验证阶段的主要作用包括

  1. 确保格式正确:验证字节码文件的格式是否符合《Java虚拟机规范》的要求,包括魔数、版本号、常量池、字段、方法、属性等结构的正确性。
  2. 确保类型安全:检查所有的类型引用是否正确,例如字段访问、方法调用、参数传递等是否使用了正确的类型。
  3. 检查访问权限:确保当前类对其他类的访问(如字段、方法的访问)遵循Java的访问控制规则,例如,私有方法不能被其他类调用。
    检查字节码完整性:确保所有的代码路径都是可达的,没有悬挂的分支或者无法到达的代码。
  4. 检查堆栈操作:验证字节码在执行过程中对操作数栈和局部变量表的操作是否合法,例如,确保操作数栈的出栈和入栈操作是匹配的。
  5. 确保符合引用规范:检查所有的符号引用(如类、字段、方法的引用)是否能够解析为实际的存在。

准备:

准备阶段是正式为类中定义的变量(静态变量)分配内存并设置初始值的阶段。

private static int value = 1;
//这个`value`的值在初始化阶段为0而不是123。

final阶段修饰的静态变量比较特殊:

private final static int value = 1;
//这个`value`的值在初始化阶段为123。

解析:

在Java虚拟机(JVM)中,解析(Resolution)是类加载过程的链接阶段的一个子阶段,它发生在类被加载和验证之后。解析的主要任务是解析类、接口、字段和方法的符号引用,将其替换为直接引用。

符号引用(Symbolic References):在编译时,Java类并不知道其他类或接口、字段和方法的实际内存地址,因此使用符号引用来代替。符号引用包含了足够的信息来描述所引用的目标,比如类名、方法名、字段名等。

直接引用(Direct References):直接引用是指向方法区中方法的指针、字段、类、接口的指针或句柄等,它们是内存中的实际地址。

初始化:

在Java虚拟机(JVM)中,初始化阶段(Initialization)是类加载过程的最后一个阶段。在这个阶段,JVM负责执行类的初始化代码,这通常包括执行静态代码块和对静态变量的赋值。初始化阶段是JVM确保类的静态字段能够正确地初始化的机制。

执行静态代码块和对静态变量的赋值合并成<clinit>方法,下面我们通过实操验证一下这个说法:
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/bc2aa0729f724e7684ca0d10df5cbd1f.png
在这里插入图片描述

这里我们没有声明静态变量和静态代码块,可以看到没有生成<clinit>方法。

在这里插入图片描述
在这里插入图片描述

这里我们声明了一个静态变量,可以看到生成了<clinit>方法。

编译器的收集顺序是由语句在源文件出现的顺序决定的,静态语句块只能访问到定义在静态语句之前的变量,定义在它之后的变量可以访问,可以赋值但是不可以访问。

public class Test01 {

    static{
        value = 20;
        System.out.println((value));  //非法向前引用
    }

    private static int value = 1;


    public static void main(String[] args) {
        System.out.println(value);
    }
}
  • 45
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值