JVM之类加载器与类加载机制

308 阅读3分钟

在上篇博客中对类的加载流程做了一个总结,这里我们详细说一下类加载器(ClassLoader)

类加载器:

类加载器根据虚拟机和开发者有两种不同的分类方式:

对于虚拟机来说,类加载器分为两种:

  • 第一种是启动类加载器,也叫引导类加载器(BootstrapClassLoader)
  • 第二中就是其他类加载器,除了上面的启动类加载器,其它类加载器都算其他类加载器

对于我们开发人员来说,类加载器又可分为以下几种:

  • 启动类加载器(BootstrapClssLoader)
  • 扩展类加载器(ExtClassLoader)
  • 应用程序类加载器(AppClassLoader)
  • 以及我们用户自定义的类加载器

启动类加载器:

  • 启动类加载器:是虚拟机的一部分,是由C/C++来编写的,用来加载我们Java的核心类,主要是JAVA_HOME/lib下的或者-Xbootclasspath参数指定的类
  • 扩展类加载器:主要是加载Java中扩展的一些类,主要负责加载JAVA_HOME/jre/lib/ext下的或则由java.ext.dirs系统变量指定的类
  • 应用程序类加载器:主要用来加载classpath路径下的所有类,

类加载器之间的关系:

除了启动类加载器之外,其他的类加载器都有自己的父类加载器,注意这里的父类加载器并不是继承关系,而是组合关系,通过调用getParent()方法可以拿到当前类加载器的父类加载器,由于启动类加载器是由C/C++编写的,Java无法直接拿到启动类加载器,所以当当前类的父类加载器为启动类加载器时调用此方法时会返回null,除启动类加载器的其他所有类加载器都直接或者间接继承自ClassLoader抽象类。

public static void main(String[] args) {
    ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
    ClassLoader extClassLoader = appClassLoader.getParent();
    ClassLoader bootstrapClassLoader = extClassLoader.getParent();

    System.out.println(appClassLoader);
    //sun.misc.Launcher$AppClassLoader@18b4aac2
    System.out.println(extClassLoader);
    //sun.misc.Launcher$ExtClassLoader@74a14482
    System.out.println(bootstrapClassLoader);
    //null

}

执行以上代码我们可以看到输出启动类加载器(bootstrapClassLoader)时结果为null

类加载器之间的具体组合关系:

类加载器.png

双亲委派机制:

当一个类加载器要加载某一个类时,会先判断该类是否已经加载过了,如果已经加载过,则直接返回加载后的类,如果没有加载过,则进行加载,在加载时会先将此类委派给父类加载器加载,父类加载器进行同样的操作(是否已加载过,否则委派给父类加载器加载),然后依次往上委派给父类加载器,直到委派给启动类加载器。

image.png

这里判断是否可以加载取决于类加载器对应的加载路径下是否有对应的类。

双亲委派机制的好处: 双亲委派机制的好处在于类会伴随着类加载器的层级关系也具有了一种具有优先级的层级关系,无论那个类加载器收到加载JAVA_HOME/lib下的类的加载请求,最终都会委派给启动类加载器,由启动类加载器加载,不同的类加载器被委派加载同一个类(全类名相同)的时候,最终都是同一个类被加载(因为会被委派给同一个类加载器)。如果我们自定义了一个java.lang.Object类放再classpath路径下由我们自定义的类加载器加载,那么他最终会被委派给启动类加载器,启动类加载器会优先加载java核心类库下的Object类,而不是我们自定义的Object类,这就保证了不会有两个同名的类被加载。