虚拟机类加载机制

Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、解析和初始化,最终造成能够被虚拟机直接使用的Java类型,这个过程被称做虚拟机的类加载机制。 ——《深刻理解Java虚拟机》html

类加载的时机

类的生命周期:

加载、链接(验证、准备、解析)、初始化、使用、卸载。 类的生命周期java

这五个阶段的顺序是肯定的,类加载必须按照这种顺序“开始”(这些阶段一般是交叉混合进行的,因此执行顺序与完成顺序可能并非按照开始顺序)。web

第一阶段“加载”啥时候开始?

《Java虚拟机规范》中并无进行强制约束,可是对于初始化阶段,则是严格规定了有且只有六种状况必须当即对类进行“初始化”(而加载、验证、准备天然须要在此之 前开始):缓存

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

接口初始化与类初始化不一样,只要是体如今上述第3点,当一个接口在初始化时,并不要求其父接口所有都完成了初始化,只有在真正使用到父接口的时候(如引用接口中定义的常量)才会初始化。安全

类加载的过程

加载

在加载阶段,Java虚拟机须要完成如下三件事情:数据结构

  1. 经过一个类的全限定名来获取定义此类的二进制字节流。
  2. 将这个字节流所表明的静态存储结构转化为方法区的运行时数据结构。
  3. 在内存中生成一个表明这个类的java.lang.Class对象,做为方法区这个类的各类数据的访问入 口。

总结一下,就是读取Class文件,将类结构存到方法区,再在堆内建立一个Class对象。oracle

链接

验证

验证是链接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚 拟机规范》的所有约束要求,保证这些信息被看成代码运行后不会危害虚拟机自身的安全。jvm

  1. 文件格式验证
  2. 元数据验证
  3. 字节码验证
  4. 符号引用验证

准备

准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段。编辑器

🌰1
//对于static变量来讲,在准备阶段结束以后,value的值是0,而非123,等到初始化时才会设置成123。 public static int value = 123;  🌰2 //而对于常量来讲,在准备阶段就会被设置成123。 public final static int value = 123; 复制代码

解析

解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程。url

符号引用就是字符串,这个字符串包含足够的信息,以供实际使用时能够找到相应的位置。你好比说某个方法的符号引用,如:“java/io/PrintStream.println:(Ljava/lang/String;)V”。里面有类的信息,方法名,方法参数等信息。

关于符号引用能够看看R大的回答。JVM里的符号引用如何存储?

还有《Java虚拟机规范》第四章class文件格式的第四节常量池 有介绍符号引用的格式。

初始化

初始化阶段就是执行类构造器clinit()方法的过程。

何为clinit()

clinit()方法是由编译器自动收集类中的全部类变量的赋值动做和静态语句块(static{}块)中的语句合并产生的。就是static变量和static方法合并后的结果。

以下图,static的变量赋值和方法会被合并到static{}(javap反编译的结果,static{}就是clinit())里。

clinit()方法
clinit()方法

类加载器

  1. BootstrapClassLoader在HotSpot中是用C++实现的,是虚拟机的一部分,负责加载JDK/jre/lib下的
  2. ExtClassLoader负责加载JDK/jre/lib/ext, java.ext.dirs系统变量指定的路径中的全部类库
  3. AppClassLoader负责加载用户类路劲下所指定的类

类加载机制

  • 全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其余Class也将由该类加载器负责载入,除非显示使用另一个类加载器来载入
  • 父类委托,先让父类加载器试图加载该类,只有在父类加载器没法加载该类时才尝试从本身的类路径中加载该类
  • 缓存机制,缓存机制将会保证全部加载过的Class都会被缓存,当程序中须要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为何修改了Class后,必须重启JVM,程序的修改才会生效

双亲委派模型

  1. 当AppClassLoader加载一个class时,它首先不会本身去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。

  2. 当ExtClassLoader加载一个class时,它首先也不会本身去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。

  3. 若是BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;

  4. ExtClassLoader也加载失败,则会使用AppClassLoader来加载,若是AppClassLoader也加载失败,则会报出异常ClassNotFoundException

使用双亲委派模型的好处

防止重复加载,防止核心库被修改。

使同名类(🌰java.lang.Object)在程序的各类类加载器环境中都可以保证是同一个类。

参考

《深刻理解Java虚拟机》《Java虚拟机规范

图片来源《深刻理解Java虚拟机》

本文使用 mdnice 排版

相关文章
相关标签/搜索