纯净、安全、绿色的下载网站

首页|软件分类|下载排行|最新软件|IT学院

当前位置:首页IT学院IT技术

Tomcat类加载机制 一篇文章讲透Tomcat的类加载机制

马坤鹏 / 孙玄   2021-11-18 我要评论
想了解一篇文章讲透Tomcat的类加载机制的相关内容吗马坤鹏 / 孙玄在本文为您仔细讲解Tomcat类加载机制的相关知识和一些Code实例欢迎阅读和指正我们先划重点:tomcat类加载机制,tomcat类加载器的加载过程,tomcat类加载器源码下面大家一起来学习吧

-     前言     -

你了解 Apache Tomcat 的类加载机制吗?本文将从底层原理切入彻底揭秘 Tomcat 类加载所涉及的源码、机制和方案助你深入掌握 Tomcat 类加载核心!

-     JVM 类加载器     -

1、JVM类加载器

说起 Tomcat 类加载器就不得不先简单说一下 JVM 类加载器如下图所示:

  • 启动类加载器:Bootstrap ClassLoader用于加载JVM提供的基础运行类即位于%JAVA_HOME%/jre/lib目录下的核 心类库
  • 扩展类加载器:Extension ClassLoader Java提供的一个标准的扩展机制用于加载除核心类库外的Jar包即只要复制 到指定的扩展目录(可以多个)下的Jar, JVM会自动加载(不需要通过-classpath指定)默认的扩展目录是%JAVA_HOME%加e/lib/ext典型的应用场景就是Java使用该类加载 器加载JVM默认提供的但是不属于核心类库的Jar不推荐将应用程序依赖的 类库放置到扩展目录下因为该目录下的类库对所有基于该JVM运行的应用程序可见
  • 应用程序类加载器:Application ClassLoader 用于加载环境变量CLASSPATH (不推荐使用)指定目录下的或者-classpath运行 参数指定的Jar包System类加载器通常用于加载应用程序Jar包及其启动入口类(Tomcat 的Bootstrap类即由System类加载器加载)

这些类加载器的工作原理是一样的区别是它们的加载路径不同也就是说 findClass 这个方法查找的路径不同

双亲委托机制是为了保证一个 Java 类在 JVM 中是唯一的假如你不小心写了一个与 JRE 核心类同名的类比如 Object 类双亲委托机制能保证加载的是 JRE 里的那个 Object 类而不是你写的 Object 类

这是因为 AppClassLoader 在加载你的 Object 类时会委托给 ExtClassLoader 去加载而 ExtClassLoader 又会委托给 BootstrapClassLoaderBootstrapClassLoader 发现自己已经加载过了 Object 类会直接返回不会去加载你写的 Object 类

这里请注意类加载器的父子关系不是通过继承来实现的比如 AppClassLoader 并不是 ExtClassLoader 的子类而是说 AppClassLoader 的 parent 成员变量指向 ExtClassLoader 对象同样的道理如果你要自定义类加载器不去继承 AppClassLoader而是继承 ClassLoader 抽象类再重写 findClass 和 loadClass 方法即可Tomcat 就是通过自定义类加载器来实现自己的类加载逻辑不知道你发现没有如果你要打破双亲委托机制就需要重写 loadClass 方法因为 loadClass 的默认实现就是双亲委托机制

2、类加载器的源码

public abstract class ClassLoader {
  //  每个类加载器都有一个父加载器
  private final ClassLoader parent;
  public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
     protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
           // 如果没有加载过
            if (c == null) {
                if (parent != null) {
                  //  先委托给父加载器去加载注意这是个递归调用
                 c = parent.loadClass(name, false);
                } else {
                 // 如果父加载器为空查找 Bootstrap 加载器是不是加载过了
                   c = findBootstrapClassOrNull(name);
                }
              
            // 如果父加载器没加载成功调用自己的 findClass 去加载
                if (c == null) {        
                    c = findClass(name);
                }
            } 
        
            return c;
        }
        
    }
    //ClassLoader 中findClass方式需要被子类覆盖下面这段代码就是对应代码
      protected Class<?> findClass(String name){
       //1. 根据传入的类名 name到在特定目录下去寻找类文件把.class 文件读入内存
          ...
       //2. 调用 defineClass 将字节数组转成 Class 对象
       return defineClass(buf, off, len)
    }
      // 将字节码数组解析成一个 Class 对象用 native 方法实现
    protected final Class<?> defineClass(byte[] b, int off, int len){
    
    }
    
}

我们自定义类加载器就需要重写ClassLoader的loadClass方法

-     Tomcat 的类加载机制     -

1、加载机制的特点

隔离性:Web应用类库相互隔离避免依赖库或者应用包相互影响设想一下如果我们 有两个Web应用一个釆用了Spring 2.5, 一个采用了Spring 4.0,而应用服务器使用一个 类加载器加载,那么Web应用将会由于Jar包覆盖而导致无法启动成功

灵活性:既然Web应用之间的类加载器相互独立那么我们就能只针对一个Web应用进行 重新部署此时该Web应用的类加载器将会重新创建而且不会影响其他Web应用如果 釆用一个类加载器显然无法实现因为只有一个类加载器的时候类之间的依赖是杂 乱无章的无法完整地移除某个Web应用的类

性能:由于每个Web应用都有一个类加载器因此Web应用在加载类时不会搜索其他 Web应用包含的Jar包性能自然高于应用服务器只有一个类加载器的情况

2、Tomcat 的类加载方案

  • 引导类加载器 和 扩展类加载器 的作⽤不变
  • 系统类加载器正常情况下加载的是 CLASSPATH 下的类但是 Tomcat 的启动脚本并未使⽤该变量⽽是加载tomcat启动的类⽐如bootstrap.jar通常在catalina.bat或者catalina.sh中指定位于CATALINA_HOME/bin下
  • Common 通⽤类加载器加载Tomcat使⽤以及应⽤通⽤的⼀些类位于CATALINA_HOME/lib下⽐如servlet-api.jar
  • Catalina ClassLoader ⽤于加载服务器内部可⻅类这些类应⽤程序不能访问
  • SharedClassLoader ⽤于加载应⽤程序共享类这些类服务器不会依赖
  • WebappClassLoader每个应⽤程序都会有⼀个独⼀⽆⼆的Webapp ClassLoader他⽤来加载本应⽤程序 /WEB-INF/classes 和 /WEB-INF/lib 下的类

tomcat 8.5 默认改变了严格的双亲委派机制:

  • 从缓存中加载
  • 如果缓存中没有会先调用ExtClassLoader进行加载 扩展类加载器是遵循双亲委派的他会调用bootstrap查看对应的lib有没有然后回退给ExtClassLoader对扩展包下的数据进行加载
  • 如果未加载到则从 /WEB-INF/classes加载
  • 如果未加载到则从 /WEB-INF/lib/*.jar 加载如果未加载到WebAppclassLoader 会委派给SharedClassLoader,SharedClassLoad会委派给CommonClassLoader.....,依次委派给BootstrapClassLoader, 然后BootstrapClassLoader 在自己目录中查找对应的类如果有则进行加载如果没有他会委派给下一级ExtClassLoader,ExtClassLoader再查找自己目录下的类如果有则加载如果没有则委派给下一级……遵循双亲委派原则

3、分析应用类加载器的加载过程

应用类加载器为WebappClassLoader 他的loadClass在他的父类WebappClassLoaderBase中

  public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            if (log.isDebugEnabled())
                log.debug("loadClass(" + name + ", " + resolve + ")");
            Class<?> clazz = null;
            // Log access to stopped class loader
            checkStateForClassLoading(name);    
            //从当前ClassLoader的本地缓存中加载类如果找到则返回
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
            // 本地缓存没有的情况下调用ClassLoader的findLoadedClass方法查看jvm是否已经加载过此类如果已经加载则直接返回
            clazz = findLoadedClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
            String resourceName = binaryNameToPath(name, false);
            //此时的javaseClassLoader是扩展类加载器  是把扩展类加载器赋值给了javaseClassLoader
            ClassLoader javaseLoader = getJavaseClassLoader();
            boolean tryLoadingFromJavaseLoader;
            try {
              .....
            //如果可以用getResource得到
            //如果能用扩展类加载器的getResource得到就证明可以被扩展类加载器加载到接下来安排扩展类加载器加载
            if (tryLoadingFromJavaseLoader) {
                try {
                    //使用扩展类加载器进行加载
                    clazz = javaseLoader.loadClass(name);
                    if (clazz != null) {
                        if (resolve)
                            resolveClass(clazz);
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
            // (0.5) Permission to access this class when using a SecurityManager
            if (securityManager != null) {
                int i = name.lastIndexOf('.');
                if (i >= 0) {
                    try {
                        securityManager.checkPackageAccess(name.substring(0,i));
                    } catch (SecurityException se) {
                        String error = "Security Violation, attempt to use " +
                            "Restricted Class: " + name;
                        log.info(error, se);
                        throw new ClassNotFoundException(error, se);
                    }
                }
            }
            boolean delegateLoad = delegate || filter(name, true);
            // (1) Delegate to our parent if requested
            //如果是true就是用父类加载器进行加载
            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
                }
            }
            // (2) Search local repositories
            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
            }
            // (3) Delegate to parent unconditionally
            //到这里还是没有加载上再次尝试使用父类加载器进行加载
            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);
    }

注:在37行英文注释中标注获取的是系统类加载器但我们debug的时候会发现他是扩展类加载器实际中我们可以推断出他应该是扩展类加载器因为如果我们加载的类在扩展类加载器路径下已经存在的话那我们直接调用系统类加载器是就是错误的了下图为debug后获取的类加载器的验证

总结

tomcat打破了双亲委派的原则实际是在应用类加载器中打破了双亲委派其他类加载器还是遵循双亲委派的


相关文章

猜您喜欢

  • @Component注解含义 关于@Component注解的含义说明

    想了解关于@Component注解的含义说明的相关内容吗Thinkingcao在本文为您仔细讲解@Component注解含义的相关知识和一些Code实例欢迎阅读和指正我们先划重点:@Component注解,@Component注解下面大家一起来学习吧..
  • Python 自动化办公 Python 自动化处理Excel和Word实现自动办公

    想了解Python 自动化处理Excel和Word实现自动办公的相关内容吗Python学习与数据挖掘在本文为您仔细讲解Python 自动化办公的相关知识和一些Code实例欢迎阅读和指正我们先划重点:Python,自动化办公,Python,Excel,Python,Word下面大家一起来学习吧..

网友评论

Copyright 2020 www.fresh-weather.com 【世纪下载站】 版权所有 软件发布

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 点此查看联系方式