聊一聊Java的JVM类加载机制

 更新时间:2023年04月20日 10:40:14   作者:索码理  
这篇文章主要介绍了聊一聊Java的JVM类加载机制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制。当Java程序运行时,Java虚拟机会按需加载类,即在程序需要使用某个类时才会加载该类。

类的生命周期如下图:

类的生命周期

JVM的类加载机制包括加载连接( 验证准备解析)、初始化 3个阶段。

加载(Loading)

加载(Loading) 阶段主要是查找并加载字节码文件,这个文件可以是来自本地文件系统、网络、jar包等地方。加载后,生成一个对应的Class对象。

加载类时会做以下工作:

  1. 根据类的全限定名查找并读取类的二进制数据。类的二进制数据可以来自文件、网络、数据库等各种数据源。
  2. 将类的二进制数据转换成方法区内部的数据结构。在转换的过程中,JVM会对类的二进制数据进行解析和校验。
  3. 在方法区内存储该类的相关信息,包括类的名称、修饰符、常量池、字段描述符、方法描述符、接口描述符、方法表等。
  4. 生成一个代表该类的Class对象,并将该对象存放在JVM的堆内存中。Class对象包含了类的各种信息,可以用于创建类的实例、获取类的方法和字段等操作。

需要注意的是,在加载类的过程中,JVM会遵循一定的双亲委派机制,即先委派给父类加载器尝试加载,如果父类加载器无法加载,则由当前类加载器进行加载。这样可以保证类的加载不会重复,避免出现类似的类被多次加载的情况。有关类加载器可以查看我之前的文章。

加载阶段与连接阶段的部分动作(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始,但这些夹在加载阶段之中进行的动作,仍然属于连接阶段的一部分,这两个阶段的开始时间仍然保持着固定的先后顺序。

连接(Linking)

连接阶段是Java虚拟机将类文件中的符号引用转换为直接引用的过程,会对字节码文件进行验证、准备、解析。

  • 验证(Verification):在这个阶段,JVM会对字节码进行验证,以确保其符合Java虚拟机规范,并且不会对虚拟机造成安全威胁。验证的内容包括静态分析、字节码验证、符号引用验证等。如果验证失败,JVM会抛出VerifyError异常。
  • 准备(Preparation):在这个阶段,JVM会为类的静态变量分配内存,并将其初始化为默认值(零值)。这个阶段不会执行任何Java代码,只是为静态变量分配内存空间。例如将int类型的静态变量赋值为0。
public static int staticValue = 123;

准备阶段初始化只是将静态变量初始化为默认值,比如上面这段代码,不同数据类型都有其默认值,初始化是只是将staticValue赋予默认值0,也就是staticValue = 0,只有在初始化阶段才会将staticValue赋值为123,也就是staticValue = 123。但是如果是staticValue是个常量public static final int staticValue = 123,准备阶段才会将staticValue赋值为123。

  • 解析(Resolution):在这个阶段,JVM会将类、接口、字段和方法的符号引用解析为直接引用符号引用是指用来描述某个类、字段或方法的名称和类型的符号,而直接引用则是指直接指向内存中的具体位置的引用。解析的过程包括将类或接口的符号引用解析为直接引用、将字段的符号引用解析为直接引用、将类中方法的符号引用解析为直接引用。

初始化(Initialization)

初始化阶段是指在类被首次主动使用时执行的阶段,虚拟机会执行类的初始化代码,包括静态变量的赋值和静态代码块的执行等操作。

初始化阶段是类加载的最后一个阶段,在该阶段,JVM会执行以下操作:

  1. 执行静态变量赋值操作:在这个阶段,JVM会对所有的静态变量进行初始化并赋值。这些静态变量的值通常在类定义中已经被明确定义了,JVM会根据定义进行相应的赋值操作。
  2. 执行静态代码块:如果类定义中包含有静态代码块,那么在该阶段JVM会执行这些静态代码块中的代码。
  3. 调用类的初始化方法:在Java程序中,可以使用static关键字来定义一个类的静态初始化方法(即static void methodName())。在该阶段,JVM会调用这个类的静态初始化方法。

类初始化的时机

类初始化时机包括以下四种情况:

  • 创建该类的实例对象时,例如使用 new 关键字创建对象,类的初始化将被触发 如果一个类是程序执行的入口类(即包含 main() 方法的类),那么也会触发该类的初始化操作。
  • 子类初始化会触发父类初始化:当一个子类被初始化时,其父类也会被初始化。这意味着,如果一个类没有被使用,那么它的父类也不会被初始化。
  • 当调用类的静态方法(不包括final方法和private方法)或访问类的静态字段(不包括final字段)时,类的初始化将被触发。
  • 当使用反射API对类进行某些操作时(例如使用Class.forName()方法加载类、调用Class.newInstance()方法创建对象、调用Method.invoke()方法调用方法等),类的初始化将被触发。

初始化是线程安全的JVM保证一个类的初始化只会由一个线程去执行,其他线程需要等待该线程完成后才能访问该类。

下面用一个简单的Java代码示例,展示JVM类加载机制中初始化阶段的示例

public class MyClass {

    // 静态变量
    public static String staticStr = "Hello, world!";

    static {
        System.out.println("MyClass is initialized.");
    }

    // 构造方法
    public MyClass() {
        System.out.println("MyClass constructor is called.");
    }

    // 静态方法
    public static void staticMethod() {
        System.out.println("MyClass staticMethod is called.");
    }
}

在上述代码中,类MyClass包含一个静态变量staticStr、一个静态代码块和一个构造方法,以及一个静态方法staticMethod。当程序首次使用MyClass类时,JVM将会触发MyClass类的初始化阶段。可以通过下面的代码来测试类的初始化:

public class Test {
    public static void main(String[] args) {
        System.out.println(MyClass.staticStr); // 调用静态变量,触发类初始化
        MyClass.staticMethod(); // 调用静态方法,触发类初始化
        MyClass obj = new MyClass(); // 创建对象,触发类初始化
    }
}

在上面的代码中,首先输出了MyClass类的静态变量staticStr,此时会触发MyClass类的初始化;然后调用了静态方法staticMethod,同样会触发MyClass类的初始化;最后创建了一个MyClass对象,也会触发MyClass类的初始化。运行上述代码,可以看到以下输出:

MyClass is initialized.
Hello, world!
MyClass staticMethod is called.
MyClass constructor is called.

输出结果表明,MyClass类的初始化确实在首次使用该类时被触发,包括静态变量、静态代码块、静态方法和构造方法都被执行了。

此外,如果一个类是另一个类的子类,那么在使用子类时,父类也会被初始化。例如:

public class MyBaseClass {
    static {
        System.out.println("MyBaseClass is initialized.");
    }
}

public class MySubClass extends MyBaseClass {
    static {
        System.out.println("MySubClass is initialized.");
    }
}

public class Test {
    public static void main(String[] args) {
        MySubClass obj = new MySubClass(); // 创建子类对象,触发父类和子类初始化
    }
}

在上述代码中,当创建MySubClass类的对象时,将会触发MyBaseClass和MySubClass类的初始化。运行上述代码,可以看到以下输出:

MyBaseClass is initialized.
MySubClass is initialized.

总结

JVM的类加载机制采用了延迟加载的策略,即在需要使用类时才加载该类,这种方式可以提高程序的启动速度,也避免了不必要的资源浪费。同时,JVM还提供了多个类加载器,可以通过自定义类加载器实现特定的加载策略,例如动态加载、远程加载等,从而满足不同的应用需求。

到此这篇关于聊一聊Java的JVM类加载机制的文章就介绍到这了,更多相关JVM类加载机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解Spring Kafka中关于Kafka的配置参数

    详解Spring Kafka中关于Kafka的配置参数

    这篇文章主要介绍了详解Spring Kafka中关于Kafka的配置参数,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-08-08
  • MyBatis-Plus中自动填充功能的用法示例详解

    MyBatis-Plus中自动填充功能的用法示例详解

    有些时候我们可能会有这样的需求,插入或者更新数据时,希望有些字段可以自动填充数据,比如密码、version、注册时默认的用户角色等,在MP中提供了这样的功能,可以实现自动填充功能,需要的朋友可以参考下
    2022-12-12
  • 如何通过一个注解实现MyBatis字段加解密

    如何通过一个注解实现MyBatis字段加解密

    用户隐私很重要,因此很多公司开始做数据加减密改造,下面这篇文章主要给大家介绍了关于如何通过一个注解实现MyBatis字段加解密的相关资料,需要的朋友可以参考下
    2022-02-02
  • Java解析pdf格式发票的代码实现

    Java解析pdf格式发票的代码实现

    为了减少用户工作量及误操作的可能性,需要实现用户上传PDF格式的发票,系统通过解析PDF文件获取发票内容,并直接将其写入表单,以下文章记录了功能实现的代码,需要的朋友可以参考下
    2024-08-08
  • springboot+jsonp解决前端跨域问题小结

    springboot+jsonp解决前端跨域问题小结

    这篇文章主要介绍了springboot+jsonp解决前端跨域问题小结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-06-06
  • Spring Security中的CORS详解

    Spring Security中的CORS详解

    CORS(Cross-Origin Resource Sharing)是一种允许不同源之间进行资源共享的W3C标准,它通过在服务器端设置特定的HTTP响应头,实现了跨域请求的功能,这种机制要求浏览器和服务器的支持,本文给大家介绍Spring Security中的CORS,感兴趣的朋友一起看看吧
    2024-10-10
  • 在项目中直接使用hystrix的流程分析

    在项目中直接使用hystrix的流程分析

    最近由于一些背景原因,需要在项目中需要对接口进行限流。所以就考虑到了直接使用Hystrix,但是呢,又不想直接使用SpringCloud,而是直接引入原生,现在发现挺好用的,所以记录下来,需要的朋友参考下吧
    2022-06-06
  • Java插入排序算法实现方法例子

    Java插入排序算法实现方法例子

    所谓排序,是将一组数据按照特定顺序重新排列的过程,稳定排序算法中相同键值的元素排序后保持原有顺序,直接插入排序和希尔排序是插入排序的两种形式,这篇文章主要介绍了Java插入排序算法实现的相关资料,需要的朋友可以参考下
    2024-10-10
  • PowerJobAutoConfiguration自动配置源码流程解析

    PowerJobAutoConfiguration自动配置源码流程解析

    这篇文章主要为大家介绍了PowerJobAutoConfiguration自动配置源码流程解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • Java图片处理之获取gif图一帧图片的两种方法

    Java图片处理之获取gif图一帧图片的两种方法

    这篇文章主要给大家介绍了关于Java图片处理之获取gif图一帧图片的两种方法,分别是利用Java原生代码和使用im4java调用ImageMagick来实现,两种方法都给出来示例代码供大家参考学习,需要的朋友们下面来一起看看吧。
    2017-10-10

最新评论