聊一聊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类加载机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 一个Servlet是如何处理多个请求的?

    一个Servlet是如何处理多个请求的?

    以前我一直以为一个Servlet只能处理一个请求,后来发现是自己太菜了,可以借助携带一个参数来完成多个请求的处理,根据参数的不同,在核心的service方法中调用不同的业务方法,来实现处理多个servlet请求的目的,废话不多说,直接上代码,需要的朋友可以参考下
    2021-06-06
  • Java二叉树的遍历思想及核心代码实现

    Java二叉树的遍历思想及核心代码实现

    今天小编就为大家分享一篇关于Java二叉树的遍历思想及核心代码实现,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • mybatis整合springboot报BindingException:Invalid bound statement (not found)异常解决

    mybatis整合springboot报BindingException:Invalid bound stateme

    这篇文章主要给大家介绍了关于mybatis整合springboot报BindingException:Invalid bound statement (not found)异常的解决办法,这个错误通常是由于Mapper文件中的statement id与Java代码中的方法名不一致导致的,需要的朋友可以参考下
    2024-01-01
  • spring-mvc/springboot使用MockMvc对controller进行测试

    spring-mvc/springboot使用MockMvc对controller进行测试

    这篇文章主要介绍了spring-mvc/springboot使用MockMvc对controller进行测试,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-11-11
  • springboot发布dubbo服务注册到nacos实现方式

    springboot发布dubbo服务注册到nacos实现方式

    这篇文章主要介绍了springboot发布dubbo服务注册到nacos实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-09-09
  • Mybatis Mapper XML文件-插入,更新,删除详解(insert, update and delete)

    Mybatis Mapper XML文件-插入,更新,删除详解(insert, updat

    这篇文章主要介绍了MyBatis的Mapper XML文件中用于插入、更新和删除数据的语句,包括这些语句的属性和子元素的使用方法
    2025-02-02
  • Spring使用@responseBody与序列化详解

    Spring使用@responseBody与序列化详解

    这篇文章主要介绍了Spring使用@responseBody与序列化详解,@responseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML数据,需要的朋友可以参考下
    2023-08-08
  • java如何利用poi解析doc和docx中的数据

    java如何利用poi解析doc和docx中的数据

    这篇文章主要给大家介绍了关于java如何利用poi解析doc和docx中数据的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • java并发编程synchronized底层实现原理

    java并发编程synchronized底层实现原理

    这篇文章主要介绍了java并发编程synchronized底层实现原理
    2022-02-02
  • RabbitMQ实现消息可靠性传递过程讲解

    RabbitMQ实现消息可靠性传递过程讲解

    消息的可靠性传递是指保证消息百分百发送到消息队列中去,这篇文章主要介绍了RabbitMQ实现消息可靠性传递过程,感兴趣想要详细了解可以参考下文
    2023-05-05

最新评论