Java虚拟机类加载机制详解

Java虚拟机(JVM)的类加载机制是其核心组成部分之一,它负责将字节码文件从磁盘加载到内存,并使其变成可以在JVM中执行的对象。理解类加载机制对于开发人员优化应用程序的启动时间、内存使用以及解决类加载相关问题非常重要。本文将深入解析Java虚拟机的类加载过程、验证步骤、初始化过程,以及类加载器的工作原理。

1. 类加载的时机

一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载、验证、准备、解析、初始化、使用和卸载七个阶段,其中验证、准备、解析三个部分统称为连接。

关于在什么情况下需要开始类加载过程的第一个阶段“加载”,Java虚拟机规范中并没有进行强制约束。 但是对于初始化阶段,严格规范了只有下面六种情况必须立即对类进行“初始化”(加载、验证、准备自然需要在此之前开始):

  1. 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类型没有进行过初始化,则需要先触发其初始化阶段。能生成这四条指令的典型Java代码场景:
    • 使用new关键字实例化对象的时候
    • 读取或设置一个类型的静态字段(被final修饰、已在编译期把结果放入调用处的那个类的常量池的静态字段除外)的时候
    • 调用一个类型的静态方法的时候
  2. 使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需要先触发其初始化
  3. 当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
  4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类
  5. 当使用JDK 7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStaticREF_putStaticREF_invokeStaticREF_newInvokeSpecial四种类型的方法句 柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化
  6. 当一个接口中定义了JDK 8新加入的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化

2. 类加载的过程

类加载过程通常分为以下五个阶段:加载、验证、准备、解析和初始化。

2.1 加载

加载阶段包括以下几个关键步骤:

  • 获取字节流:通过类的全限定名获取类的二进制字节流。
  • 数据转换:将字节流转换为JVM内部的类数据结构。
  • 生成Class对象:在内存中生成一个java.lang.Class对象,作为访问类数据的入口。

2.2 验证

验证是连接过程中的第一步,旨在确保字节码文件符合Java虚拟机规范,保证类不会对虚拟机造成安全威胁。验证包括:

2.2.1 文件格式验证

确保字节码符合class文件格式的规范。包括检查文件是否以0xCAFEBABE开头、类版本是否支持等。

2.2.2 元数据验证

对字节码描述的信息进行语义检查,确保符合Java语言规范。比如,类是否具有父类,字段和方法是否符合规范等。

2.2.3 字节码验证

通过数据流和控制流分析,确保字节码在执行时不会危害虚拟机的安全。JDK6之后,字节码验证得到优化,虚拟机通过StackMapTable属性进行类型检查,减少了验证的复杂度。

2.2.4 符号引用验证

符号引用验证发生在解析阶段,将常量池中的符号引用转换为直接引用时,确保类、字段、方法等的访问权限和存在性。

2.3 准备

准备阶段为类的静态变量分配内存并设置初始值。所有静态变量会被初始化为默认值,例如0null等。对于final修饰的静态常量,虚拟机会直接在此阶段赋予其编译时已知的值。

2.4 解析

解析阶段是将常量池中的符号引用替换为直接引用的过程。解析过程包括类、字段、方法、方法句柄等的解析。符号引用与虚拟机内存布局无关,可以在运行时动态解析。

2.5 初始化

初始化阶段执行类中的静态初始化代码(即<clinit>()方法)。<clinit>()方法由编译器自动生成,包含静态字段赋值和静态代码块的执行。此阶段的关键点是:

  • 静态字段赋值和静态代码块的执行顺序按源代码中的顺序执行。
  • 在多线程环境中,JVM会确保一个类的初始化操作只会由一个线程执行,避免多线程争用。

3. 类加载器

类加载器是JVM中负责加载类的组件。它的工作流程通常是通过类的全限定名获取字节流,解析后加载到内存中。类加载器可以根据不同的需求进行扩展或定制。

3.1 双亲委派模型

Java采用了双亲委派模型来管理类加载器的层次结构。所有的类加载请求首先会被委派给父类加载器处理,只有当父加载器无法加载时,子加载器才会尝试加载。JVM默认有以下几种类加载器:

  • 启动类加载器(Bootstrap ClassLoader):负责加载核心类库(如rt.jar中的类)。
  • 扩展类加载器(Extension ClassLoader):加载JVM扩展库(如JAVA_HOME/lib/ext目录中的类)。
  • 应用类加载器(Application ClassLoader):加载用户指定的类路径中的类。

类加载器双亲委派模型:

通过双亲委派模型,Java虚拟机确保了类加载的稳定性和层次性。如果没有双亲委派模型,程序中可能会出现多个java.lang.Object类等问题,导致系统的不稳定,双亲委派模型对于保证Java程序的稳定运作极为重要,但它的实现非常简单。用以实现双亲委派模型的代码只有10行左右,全部在java.lang.ClassLoader的loadClass()方法之中。

protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
        //首先,检查请求的类是否已经被加载过了
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                //如果父类加载器抛出ClassNotFoundException说明父类加载器无法完成加载请求
            }

            if (c == null) {
                //在父类加载器无法加载时,再调用本身的findClass()方法来进行类加载
                c = findClass(name);
            }
        }
        return c;
}

核心逻辑:先检查请求加载的类型是否已经被加载过,如果没有则调用父加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。假如父类加载器加载失败,抛出ClassNotFoundException异常的话,才调用自己的findClass()方法尝试进行加载

4. 总结

Java虚拟机的类加载机制是一个复杂且高效的过程,包括加载、验证、准备、解析和初始化等多个阶段。每个阶段都有严格的规范,确保程序在不同环境中稳定运行。理解类加载的时机、过程和类加载器的工作原理,有助于开发者更好地掌握JVM,优化应用程序的性能,解决类加载相关的问题。掌握双亲委派模型和类加载器的实现机制,对深入理解JVM和进行系统优化尤为重要。

🌟 关注我的CSDN博客,收获更多技术干货! 🌟

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dm菜鸟编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值