JVM类加载机制

一、JVM的类加载阶段

JVM的类加载分为5个阶段:加载、验证、准备、解析、初始化。在类初始化完成后就可以使用该类的信息,在一个类不再被需要时就可以从JVM中卸载。如图所示:

 1、加载

JVM读取class文件并根据class文件描述创建java.lang.Class对象的过程。类加载过程主要包含将class文件读取到方法区内,在堆中创建java.lang.Class对象,并封装类在方法区的数据结构的过程,在读取class文件时即可以通过文件的形式读取,也可以通过jar包、war包读取,还可以通过代理自动生成class或其他方式读取。

2、验证

主要用于确保class文件符合当前虚拟机的要求,保障虚拟机自身的安全,只有通过验证的class文件才被JVM加载。

3、准备

主要工作是在方法区中为类变量分配内存空间并设置类中变量的初始值(不同数据类型的默认值),这里需要注意final类型变量和非final类型变量在准备阶段的数据初始化过程不同。比如一个成员变量定义如下:

 public static Long value = 1000L; 

在以上代码中,静态变量value在准备阶段的初始值是0,将value设置为1000的动作是在类的class对象初始化时完成的,因为JVM在编译阶段会将静态变量的初始化操作定义在构造器中。但是,如果将value设置为final类型:

 public static final Long value = 1000L; 

则JVM在编译阶段后会为final类型的变量value生成其对应的ConstantValue属性,虚拟机在准备阶段会根据ConstantValue属性将value赋值为1000。

 4、解析

JVM会将常量池中的符号引用替换为直接引用。

5、初始化

通过执行类构造器的<client>方法为类进行初始化。<client>方法是在编译阶段由编译器自动收集类中静态代码块和变量的赋值操作组成的。JVM规定,只有在父类的<client>方法都执行成功后,子类中的<client>方法才可以被执行。在一个类中既没有静态变量赋值操作也没有静态代码块时,编译器不会为该类生产<client>方法。

在以下几种情况时,JVM不会执行类的初始化流程:

  • 常量在编译时会将其常量值存入使用该常量的类的常量池中,该过程不需要调用常量所在的类,因此不会触发该常量所在类的初始化。
  • 在子类引用父类的静态字段时,不会触发子类的初始化,只会触发父类的初始化。
  • 定义对象数组,不会触发该类的初始化。
  • 在使用类名获取Class对象时不会触发该类的初始化。
  • 在使用Class.forName加载指定的类时,可以通过initialize参数设置是否需要对类进行初始化。
  • 在使用ClassLoader默认的loadClass方法加载类时不会触发该类的初始化。

二、类加载器

JVM提供3种类加载器,分别是启动类加载器、扩展类加载器、应用程序类加载器。如图所示:

1、启动类加载器

负责加载java_HOME/lib目录中的类库,或通过-Xbootclasspath参数指定路径中被虚拟机认可的类库。

2、扩展类加载器

 负责加载java_HOME/lib/ext目录中的类库,或通过java.ext.dirs系统变量指定路径中的类库。

3、应用程序类加载器

负责加载用户路径(classpath)中的类库。

开发人员还可通过继承java.lang.ClassLoader实现自定义的类加载器。

三、双亲委派

JVM通过双亲委派机制对类进行加载。双亲委派机制是指一个类加载器在收到类加载请求后不会尝试自己去加载这个类,而是把这个类加载的请求向上委派给其父加载器去完成,其父加载器在接收到这个类加载请求后又会将请求委派给自己的父加载器,以此类推,这样所有的类加载请求都被向上委派到启动类加载器中。若父类加载器在接收到类加载请求后发现自己也无法加载该类(通常是因为该类的Class文件不在当前类加载器的加载路径中),则父类加载器会将该信息反馈给子类加载器并向下委派子类加载器加载这个类,直到该类被成功加载,若没有类加载器能找到并加载这个类,则JVM会抛出ClassNotFoud异常。

双亲委派类加载机制的类加载流程如图所示:

(1)将自定义类加载器挂载到应用程序类加载器。

(2)应用程序类加载器将类加载器请求委托给扩展类加载器。

(3)扩展类加载器将类加载器请求委托给启动类加载器。

(4)启动类加载器在加载路径下查找并加载目标class文件,如果未找到目标class文件,则交由扩展类加载器加载。

(5)扩展类加载器在加载路径下查找并加载目标class文件,如果未找到目标class文件,则交由应用程序类加载器加载。

(6)应用程序类加载器在加载路径下查找并加载目标class文件,如果未找到目标class文件,则交由自定义类加载器加载。

(7)自定义类加载器在加载路径下查找并加载目标class文件,如果未找到目标class文件,则JVM抛出ClassNotFoud异常。

双亲委派机制的核心是保障类的唯一性和安全性。例如在加载rt.jar包中的java.lang.Object类时,无论是哪个类加载器加载这个类,最终都将类加载请求委托给启动类加载器,这样就保证了类加载的唯一性。如果在JVM中存在包名和类名相同的两个类,则该类无法被加载,JVM也无法完成类加载流程。

当在使用Class.forName加载指定类时会破坏双亲委派机制,如JDBC中加载驱动类。

 

posted @ 2020-10-10 20:14  西北-孤狼  阅读(203)  评论(0)    收藏  举报