详解JVM虚拟机的类加载机制

 更新时间:2023年08月14日 10:22:08   作者:haihui_yang  
这篇文章主要介绍了详解JVM虚拟机的类加载机制,虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的类加载机制,需要的朋友可以参考下

1、什么是虚拟机的类加载机制?

虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型。

这就是虚拟机的类加载机制(通俗一点讲就是把 Class 转成虚拟机可以使用的 Java 类型,转换过程中会有校验、解析和初始化的操作)。

2、类的生命周期:

类从被加载到虚拟机内存开始,到卸载出内存为止,它的生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)七个阶段。其中,验证、准备、解析三个部分统称为连接(Linking)。如下图所示:

类的生命周期

图中需要注意的地方:加载、验证、准备、初始化和卸载这五个阶段具有确定的顺序。

这个确定的顺序是指"开始",即验证阶段必须等加载开始之后才能开始,准备阶段开始必须在验证开始之后。而 “进行” 和 “完成” 的顺序则不一定,这些阶段通常都是可以互相交叉地混合式进行的,通常会在一个阶段执行的过程中调用、激活另外一个阶段。

3、触发类初始化的条件(有且只有五种情况):

  • new 一个对象的时候、读取或设置一个类的静态字段(放入常量池的静态字段除外:被 final 修饰的 static 变量)的时候,以及调用一个类的静态方法的时候。
  • 反射调用的时候。
  • 子类初始化触发父类初始化。
  • 执行的主类(包含 main() 方法的那个类)。
  • 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果是 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄的时候。

下面几个例子是类的被动引用,不会触发类的初始化:

(1)通过子类引用父类的静态字段,不会导致子类初始化

public class SuperClass {
    static {
        System.out.println("SuperClass init!");
    }
    public static int value = 123;
}
public class SubClass extends SuperClass {
    static {
        System.out.println("SubClass init!");
    }
}
public class NotInitialization {
    public static void main(String[] args) {
        System.out.println(SubClass.value);//通过子类引用父类的静态字段,不会导致子类初始化
    }
}

(2)通过数组定义来引用类,不会触发此类的初始化

public class NotInitialization {
    public static void main(String[] args) {
        SuperClass[] superClassArray = new SuperClass[10]; // 通过数组定义来引用类,不会触发此类的初始化
    }
    // 这段代码不会输出 "SuperClass init!",即没有触发 SuperClass 的初始化
}

(3)常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化

public class ConstClass {
    static {
        System.out.println("ConstClass init!");
    }
    public static final String HELLO_WORLD = "hello world";
}
public class NotInitialization {
    public static void main(String[] args) {
        System.out.println(ConstClass.HELLO_WORLD); // 引用常量池的变量不会触发定义常量的类的初始化
    }
    // 不会输出 "ConstClass init!"
}

4、类加载的过程 加载

  • 加载阶段,虚拟机通过一个类的全限定名来获取定义此类的二进制流,然后把二进制流的静态存储结构转化为方法区的运行时数据结构,最后在内存中生成一个对象,作为方法区这个类各种数据的访问入口。
  • 验证(非常重要但非必要)

验证的目的在于确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。(假如自己编写的及第三方库都已经被反复使用和验证过,那么,可以考虑使用 -Xverify:none 参数关闭大部分的类验证机制,以缩短虚拟机类加载的时间)

验证阶段大致会完成以下四个验证动作:

(1)文件格式验证

(2)元数据验证

(3)字节码验证

(4)符号引用验证

  • 准备

准备阶段是给类成员变量设置初始值(数据类型的零值)。

这里的类成员变量指被 static 修饰的变量;初始值指数据类型的零值;而常量(被 static final 修饰的变量)会初始化为具体的值。

例如:

public static final int value = 123;
// 虚拟机在准备阶段就会根据 ConstantValue 的设置将 value 赋值为 123。

数据类型的零值:

数据类型的零值

  • 解析

解析阶段主要做的事情是虚拟机将常量池内的符号引用替换成直接引用。

Java 中编译时 java.lang.NoSuchFieldError java.lang.NoSuchMethodError java.lang.IllegalAccessError 等异常就是在解析阶段抛出的。

  • 初始化

初始化阶段是类加载过程的最后一步。前面我们提到,准备阶段会给类变量设置初始零值,而在初始化阶段,则是给类变量执行代码里真正赋值的时候。

  • 赋值操作由所有类变量的赋值动作和静态语句块中的语句共同产生,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问,例如:
public class Test {
    static {
        i = 0; // 静态语句块可以给后面定义的类变量赋值,但是不能访问(编译器会报 "非法向前引用" )
        // System.out.println(i); Illegal forward reference.
    }
    static int i = 1;
}

初始化阶段的注意事项:

(1)静态成员变量初始化在静态方法之前( static {}  中都是变量赋值);

(2)父类初始化在子类之前

(3)执行接口的初始化的时候不需要先执行父接口的初始化,只有当父接口中定义的变量使用时,父接口才会初始化;

(4)虚拟机会保证一个类的初始化方法在多线程环境中被正确地加锁、同步:保证了在同一个类加载器下,一个类型只会初始化一次。

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

相关文章

  • Java设计模式之Iterator模式介绍

    Java设计模式之Iterator模式介绍

    所谓Iterator模式,即是Iterator为不同的容器提供一个统一的访问方式。本文以java中的容器为例,模拟Iterator的原理。需要的朋友可以参考下
    2013-07-07
  • SpringCloud中的Feign远程调用接口传参失败问题

    SpringCloud中的Feign远程调用接口传参失败问题

    这篇文章主要介绍了SpringCloud中的Feign远程调用接口传参失败问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Mybatis拦截器打印sql问题

    Mybatis拦截器打印sql问题

    这篇文章主要介绍了Mybatis拦截器打印sql问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • 怎么在AVD上安装apk软件

    怎么在AVD上安装apk软件

    执行 Windows 开始菜单 => 所有程序 => 附件 => 命令提示符 或通过 Win+R 组合键调出 运行 对话框输入cmd 单击确定即可
    2013-09-09
  • 实例讲解Java的Spring框架中的AOP实现

    实例讲解Java的Spring框架中的AOP实现

    这篇文章主要介绍了Java的Spring框架中的AOP实现实例,AOP面向切面编程其实也可以被看作是一个设计模式去规范项目的结构,需要的朋友可以参考下
    2016-04-04
  • 理解Java访问权限控制

    理解Java访问权限控制

    这篇文章主要帮助大家深入的理解Java访问权限控制,为何需要访问控制权限,本文给出了解释,感兴趣的小伙伴们可以参考一下
    2016-02-02
  • Java项目Guava包 HashMultimap使用及注意事项

    Java项目Guava包 HashMultimap使用及注意事项

    guava基本上可以说是java开发项目中,大概率会引入的包,今天介绍的主角是一个特殊的容器HashMultmap,可以简单的将它的数据结构理解为Map<K, Set<V>>,今天主要介绍下基础的知识点 HashMultmap级使用,感兴趣的朋友一起看看吧
    2022-05-05
  • JMeter配置元件详解

    JMeter配置元件详解

    JMeter提供了丰富的配置元件,常用的包括参数化配置元件、HTTP请求默认值、HTTP信息头管理器、计数器等,本文就详细的介绍一下这些元件的使用,感兴趣的可以了解一下
    2021-12-12
  • Java可变个数形参的方法实例代码

    Java可变个数形参的方法实例代码

    这篇文章主要给大家介绍了关于Java可变个数形参的相关资料,文中通过图文以及实例代码介绍的非常详细,对大家学习或者使用java具有一定的参考学习价值,需要的朋友可以参考下
    2022-02-02
  • 浅谈Java中格式化输出

    浅谈Java中格式化输出

    这篇文章主要介绍了Java中格式化输出,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04

最新评论