JVM(6)-类的加载

类加载的过程包括5个阶段,分别是“加载”,“验证”,“准备”,“解析”,“初始化”。本文简单介绍一下和加载过程相关的类加载器和双亲委派模型。

类加载器

什么是类加载器?简单说,类加载器就是负责指定全限定名称将class文件加载到JVM内存,转为Class对象。这个加载过程就是由类加载器来完成。

从Java虚拟机的角度出发,只存在两种不同的类加载器,一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用 C++ 语言实现,是虚拟机自身的一部分;另一种是所有其它的类加载器,这些类加载器都继承自抽象类ClassLoader。但是从Java开发人员的角度来看,类加载器可以细分为如下四种:

  1. 启动类加载器(Bootstrap ClassLoader)
    负责将存放在%JAVA_HOME%/lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机按照文件名识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。
      
    启动类加载器无法被Java程序直接引用。

    JDK 中的源码类大都是由启动类加载器加载,比如开发中经常用到的java.lang.Stringjava.util.List等,需要注意的是,启动类 main Class 也是由启动类加载器加载。

  2. 扩展类加载器(Extension ClassLoader)
    扩展类加载器即sun.misc.Launcher$ExtClassLoader这个内部类,负责加载%JAVA_HOME%/lib/ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。

    开发者可以直接使用扩展类加载器。

  3. 应用程序类加载器(Application ClassLoader)
    应用程序类加载器即sun.misc.Launcher$AppClassLoader这个内部类。由于这个类加载器是 ClassLoader.getSystemClassLoader() 方法的返回值,所以一般也称它为系统类加载器。

    它负责加载用户类路径ClassPath上所指定的类库,开发者可以直接使用这个类加载器。如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

    通常项目中自定义的类,都会放在类路径下,由应用程序类加载器加载。

  4. 自定义类加载器(User ClassLoader)

    这是由用户自己定义的类加载器,一般情况下我们不会自定义类加载器,但有些特殊情况,比如JDBC能够通过连接各种不同的数据库就是自定义类加载器来实现的。

双亲委派模型

双亲委派机制就是指:如果一个类加载器收到了类加载请求,它首先不会自己尝试去加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有父类加载器反馈到无法完成这个加载请求(它的搜索范围没有找到这个类),子加载器才会尝试自己去加载。注意,这里的父类加载器并不是说类加载器之间有继承关系,只是加载流程上的顺序关系。

怎么理解这个双亲委派机制呢,我们上面介绍了4种类加载器,起始这4种类加载器是存在层级关系的,示意图如下:

比如 Application ClassLoader 接收到了一个类的加载请求,那么这个类加载器会查找当前加载缓存,看这个类是否已经被加载过,如果缓存中查到了则返回,如果没有查到会将加载请求传递给父级的类加载器,当传递到 Bootstrap ClassLoader 后还未从缓存中找到该类则向上委托结束。接下来是向下查找的过程,此时加载请求在 BootStrap ClassLoader 处,它会检查自己的加载路径下是否有该类,没有则向下委托下级类加载器在它的加载路径进行查找加载,如果没有则继续向下传递至 Application ClassLoader 结束。如果最终 Application ClassLoader 也没有加载到该类,则会抛出ClassNotFound异常。

双亲委派机制可以用一句话总结就是:向上委托查找,向下委托加载

双亲委派机制的好处

  1. 主要是为了安全性,避免用户编写的类替换 Java 的一些核心类,比如java.lang.String
  2. 同时也避免了类的重复加载,因为 JVM中区分不同类,不仅仅是根据类名,相同的 class 文件被不同的 ClassLoader 加载后就会被认为是不同的两个类。通过委派父类加载器加载的方式可以保证一个class只会被加载一次。
------ 本文完 ------