Tomcat-聊聊ClassLoader的那些事儿

1,152 阅读3分钟

一、聊聊ClassLoader的那些事儿

  我们要分析清楚Tomcat中的类加载器相关的内容之前我们还是需要把JVM中的类加载器给大家理清楚。

1.类加载器的过程

  类加载器的作用就是从文件系统或者网络中加载Class文件,至于他是否可以运行就不是ClassLoader的工作了。

image.png

2.类加载器的分类

  JVM中支持的类加载器有两种类型,分别是 引导类加载器【Bootstrap ClassLoader】和 自定义类加载器【User-Defined ClassLoader】

image.png

  在Java虚拟机层面定义:所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器。

image.png

可以通过源码看到对应的类加载器的继承关系

ExtClassLoader

image.png

AppClassLoader

image.png

通过具体的案例代码可以来看看类加载器的使用

public static void main(String[] args) {
        // 获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println("systemClassLoader = " + systemClassLoader);
        // 获取父类加载器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println("parent = " + parent);
        // 继续获取上层的类加载器
        ClassLoader bootstrapClassLoader = parent.getParent();
        System.out.println("bootstrapClassLoader = " + bootstrapClassLoader);
        // 自定义Java类
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println("classLoader = " + classLoader);
        // Java系统类
        ClassLoader classLoader1 = String.class.getClassLoader();
        System.out.println("classLoader1 = " + classLoader1);
    }

对应的输出结果

systemClassLoader = sun.misc.Launcher$AppClassLoader@18b4aac2
parent = sun.misc.Launcher$ExtClassLoader@61bbe9ba
bootstrapClassLoader = null
classLoader = sun.misc.Launcher$AppClassLoader@18b4aac2
classLoader1 = null

3.Bootstrap ClassLoader

  虚拟机自带的类加载器,启动类加载器

  • 通过C/C++实现的,JVM内置类加载器
  • 作用是用来加载Java的核心库(JAVA_HOME/jre/lib.rt.jar、resources.jar或者sun.boot.class.path路径下的内容)、用于提供JVM自身需要的类。
  • 没有继承java.lang.ClassLoader、没有父加载器,自己就是祖先了。
  • 加载扩展类和应用程序类加载器,并指定为他们的父类加载器
  • 出于安全考虑,Bootstrap启动类加载器只加载报名为java,javax,sun等开头的类

通过代码来看看具体的加载路径有哪些

    public static void main(String[] args) {
        System.out.println("**************启动类加载器*************");
        URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
        for (int i = 0 ; i < urLs.length ; i ++){
            URL urL = urLs[i];
            System.out.println("urL = " + urL.toExternalForm());
        }
    }

输出结果:

**************启动类加载器*************
urL = file:/D:/software/java/jdk8/jre/lib/resources.jar
urL = file:/D:/software/java/jdk8/jre/lib/rt.jar
urL = file:/D:/software/java/jdk8/jre/lib/sunrsasign.jar
urL = file:/D:/software/java/jdk8/jre/lib/jsse.jar
urL = file:/D:/software/java/jdk8/jre/lib/jce.jar
urL = file:/D:/software/java/jdk8/jre/lib/charsets.jar
urL = file:/D:/software/java/jdk8/jre/lib/jfr.jar
urL = file:/D:/software/java/jdk8/jre/classes

4.Extension ClassLoader

  虚拟机自带的加载器。扩展类加载器,Java语音编写,是sun.misc.Launcher的内部类

image.png

派生于ClassLoader所以是自定义类加载器

image.png

父类加载器是BootstrapClassLoader。

image.png

  扩展类加载器是从扩展目录 java.ext.dirs系统属性指定的目录中加载类库,或者从JDK的安装目录的 jre/lib/ext子目录下加载雷凯,如果用户创建的jar包也放在了这个目录下,那么该类加载器也会自动加载的。

然后通过案例来看看扩展类加载器加载的路径

    public static void main(String[] args) {
        System.out.println("**************扩展类加载器*************");
        String extDirs = System.getProperty("java.ext.dirs");
        System.out.println("extDirs = " + extDirs);
    }

对应的输出结果

**************扩展类加载器*************
extDirs = D:\software\java\jdk8\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext

然后通过加载路径下的Java类测试也能证明

image.png

5.App ClassLoader

  虚拟机自带的加载器,APPClassLoader也叫应用程序类加载器。Java语音编写,由sun.misc.Launcher下的内部类提供。也是派生于ClassLoader。

image.png

父类加载器为ExtClassLoader

image.png

  主要负责加载环境变量classpath或系统属性 java.class.path 指定路径下的类库,该类加载器是程序中的默认类加载器,一般来说,Java应用的类都是由它来完成加载的。通过 ClassLoader#getSystemClassLoader() 方法可以获取到该类加载器。

6.自定义类加载器

  在我们的日常应用程序的开发中,我基本是用不到自定义类加载器的,基本就是由前面介绍的这三个类加载器来相互配合搞定的。但是在有些特殊的情况下我们不希望通过系统的类加载器来处理,这时我们就可以通过自定义类加载来实现了。

  用户自定义类加载器的实现步骤:

  1. 我们可以直接编写 java.lang.ClassLoader 类的实现来完成自定义的处理
  2. 在JDK1.2之前,自定义类加载器时我们总是会继承ClassLoader然后重写loadClass方法,达到自定义类加载的目的,但是在JDK1.2之后建议把自定义的类加载逻辑放在findClass()方法中
  3. 最后在编写自定义类加载的时候,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样可以达到简化自定义类加载器的目的

7.双亲委派机制

Java虚拟机对class文件采用的是 按需加载的方式,也就是当需要使用该类时才会将他的class文件加载到内存中生成class对象,而且加载某个类的class文件时,Java虚拟机采用的而是双亲委派模式。即把请求交由父类加载器处理,它是一种任务委派模式。

image.png

双亲委派的工作原理:

  1. 如果一个类加载器收到了类加载的请求,它并不会自己先去加载,而且把这个请求委托给父类的加载器去执行。
  2. 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归请求,最终将请求流转到顶层的启动类加载器
  3. 如果父类加载器可以完成类加载任务,就成功返回,如果父类无法完成任务,子加载器才会尝试自己去加载。

举个简单的例子,我们自定义一个java.lang.String类,然后添加对应的main方式执行。

public class String {
    public static void main(String[] args) {
        System.out.println("自定义String类");
    }
}

报错信息为:

image.png

原因就是双亲委派机制通过BootstrapClassLoader加载的是java包下的String,而不会加载我们自定义的。

双亲委派机制的有点:

  1. 避免类的重复加载
  2. 保护程序的安全,防止核心API被随意的篡改

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第22天,点击查看活动详情