类加载 双亲委派机制 源码解析Tomcat的类加载机制

目录

前言

双亲委派机制

双亲委派机制的实现

双亲委派机制的好处

如何破坏双亲委派机制

loadClass 方法和 findClass 方法

为什么 TOMCAT 要破坏双亲委派机制

Tomcat 的类加载机制

源码

图解

拓展


前言

我们常说的是 一个类的加载 第一步就是加载

加载阶段 我们会将类的,class 文件加载到 JVM 里面

在这个阶段

JVM 会根据类的全限类名来获取定义该类的二进制字节流

并且将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

这个加载过程会创建一个 java.lang.Class 类的实例来表示这个类

这个 Class 对象作为的事程序中每个类的数据访问入口

双亲委派机制

双亲委派机制 规定了 除了最顶层的启动类加载器(BootStrap ClassLoader) 其余的类加载器都有自己的父类加载器

如果一个类加载器接收到类加载请求

首先他自己不会尝试去加载这个类 而是委派给父类加载器完成

因此所有的加载类请求都会传送到顶层的启动类加载器

只有顶层的启动类加载器反馈自己无法完成加载请求

子加载器才会自己去加载

双亲委派机制的实现

双亲委派机制的实现比较简单

  1. 先检查类是否被加载过
  2. 调用父类加载器的 loadClass() 方法 往上抛
  3. 父类加载失败 调用自己的 findClass() 方法

双亲委派机制的好处

让重要类如 Object 在程序的各个环境中这都是同一个类

如何破坏双亲委派机制

自定义类加载器

重写 loadClass()方法

loadClass 方法和 findClass 方法

findClass 用于重写类的加载逻辑

loadClass 方法的逻辑里如果父类加载器失败了 就会调用自己的 findClass 方法

为什么 TOMCAT 要破坏双亲委派机制

一个 web 容器可能要部署多个应用程序

不同的程序可能会依赖同一个第三方库的不同版本

而可能会存在某个类的全路径是一样的

Tomcat 的类加载机制

阅读源码我们发现

类加载机制大体上是大同小异

第一步依旧是尝试 本地缓存 还是 Jre Tomcat 里是不是已经加载了类 缓存命中的话 直接返回缓存中的 Class 对象

第二步还是抛给 BootStarpClassLoader 顶层的启动类加载器尝试加载

第三步 无法加载 (通过一个布尔值 delegate 属性判断) 再委派给 WebAppClassLoader SystemClassLoader CommonClassLoader...

第四步 自己尝试加载 findClass()

第五步 没有加载到 在委派给上层加载器

源码

public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

    //加锁,防止并发
    synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
        if (log.isDebugEnabled()) {
            log.debug("loadClass(" + name + ", " + resolve + ")");
        }
        Class<?> clazz = null;

        // ...

        // 检查本地缓存是否已加载该类,如果是,则直接返回缓存中的 Class 对象。
        clazz = findLoadedClass0(name);
        if (clazz != null) {
            if (log.isDebugEnabled()) {
                log.debug("  Returning class from cache");
            }
            if (resolve) {
                resolveClass(clazz);
            }
            return clazz;
        }

        // 检查另一个类加载缓存,如果是GraalVM环境,直接返回缓存中的 Class 对象。
        clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name);
        if (clazz != null) {
            if (log.isDebugEnabled()) {
                log.debug("  Returning class from cache");
            }
            if (resolve) {
                resolveClass(clazz);
            }
            return clazz;
        }

        /*
         * 尝试使用Bootstrap类加载器加载类,以防止Web应用程序覆盖Java SE类。如果加载成功,则返回加载的 Class 对象。
         */
        String resourceName = binaryNameToPath(name, false);

        ClassLoader javaseLoader = getJavaseClassLoader();
        boolean tryLoadingFromJavaseLoader;
        try {

            URL url = javaseLoader.getResource(resourceName);
            tryLoadingFromJavaseLoader = url != null;
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            tryLoadingFromJavaseLoader = true;
        }

        if (tryLoadingFromJavaseLoader) {
            try {
                clazz = javaseLoader.loadClass(name);
                if (clazz != null) {
                    if (resolve) {
                        resolveClass(clazz);
                    }
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }

        boolean delegateLoad = delegate || filter(name, true);

        // 根据 delegate 属性和其他条件判断是否应该委派加载给父类加载器。
        // 如果需要委派,则直接先进行委派
        if (delegateLoad) {
            if (log.isDebugEnabled()) {
                log.debug("  Delegating to parent classloader1 " + parent);
            }
            try {
                clazz = Class.forName(name, false, parent);
                if (clazz != null) {
                    if (log.isDebugEnabled()) {
                        log.debug("  Loading class from parent");
                    }
                    if (resolve) {
                        resolveClass(clazz);
                    }
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }

        // 自己尝试加载
        // 能走到这里,肯定是BootStrap没加载到,之后还有两种情况:
        // 1、如果delegate为ture的话,说明上层类加载器也没记载到。
        // 2、如果delegate为false,那么就还没有进行过委派,先在这里尝试自己加载。
        if (log.isDebugEnabled()) {
            log.debug("  Searching local repositories");
        }
        try {
            clazz = findClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled()) {
                    log.debug("  Loading class from local repository");
                }
                if (resolve) {
                    resolveClass(clazz);
                }
                return clazz;
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }

        // 如果delegate为false,说明还没有做过委派,那么委派给父类加载器加载类。
        if (!delegateLoad) {
            if (log.isDebugEnabled()) {
                log.debug("  Delegating to parent classloader at end: " + parent);
            }
            try {
                clazz = Class.forName(name, false, parent);
                if (clazz != null) {
                    if (log.isDebugEnabled()) {
                        log.debug("  Loading class from parent");
                    }
                    if (resolve) {
                        resolveClass(clazz);
                    }
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }
    }

    throw new ClassNotFoundException(name);
}

图解

拓展

Tomcat 中 每个应用都有自己的 WebappClassLoader 进行加载

他们是没有委派关系的

保证了类重复加载冲突

实现了 Web 应用程序的隔离和独立运行

但是这样很浪费

Tomcat 其实有个方案 就是 SharedClassLoader 加载出来的类在所有 Web 应用程序中是共享的