撸猫日记|源码剖析Tomcat的类加载器到底有何不同?

10 篇文章 1 订阅
4 篇文章 1 订阅

Tomcat源码剖析——类加载器

本文解析源码来自于Tomcat8.5.33

本文引用参考文献为《深入理解JVM虚拟机-周志明》、《Tomcat架构解析-刘光瑞》

注:此文为连载文章,后续将更新文章《初始化》


概念

Tomcat作为一款开源的轻量级应用服务器,它能够同时为多个Web应用进行服务。Tomcat是运行在JVM上的服务器,那么各个服务之间,如何保障加载的类不会冲突?这就靠的是Tomcat的类加载器设计;

JVM类加载机制

谈及Tomcat的类加载器,那么不得不提一下虚拟机的类加载机制。

定义:JVM把描述类的数据从class文件加载到内存并对数据进行校验、解析,初始化最终形成可以被虚拟机直接使用的Java类型;

流程:具体流程可参考《深入理解JVM虚拟机》中类加载机制章节和本人的相关文章;

在这里插入图片描述

加载机制的核心就是类加载器。关于Java的类加载器,不得不提一下Java牛逼的双亲委派模型;

双亲委派原理:除了顶层的启动类加载器外,其余加载器都应有自己的父类加载器。每次收到类加载请求,都会委派给父类加载器完成,若父类加载器无法加载,子加载器才会尝试自己加载。

双亲委派模型
  1. 启动类加载器(Boostrap Class Loader)

    这个类加载器负责加载存放在JAVAHOME\jre\lib目录下,或被-Xbootclasspath指定的路径中。JVM能够识别的类库加载到虚拟机的内存中。该加载器无法被java程序直接使用。

  2. 扩展类加载器(Extension Class Loader)

    他负责加载JAVAHOME\jre\lib\ext目录中或指定路径的所有类库。JDK允许用户将具有通用性的类库放置在ext目录里扩展SE的功能。

  3. 应用程序类加载器(Application/System Class Loader)

    是ClassLoader.getSystemClassLoader()返回值。负责加载用户类路径上所有的类库。程序中的默认类加载器。

在这里插入图片描述

Tomcat类加载机制特性

首先我们要明确一点,如果一个class文件是由同一个类加载器加载的,那么其Java类型是相同的。如果一个class文件是由两个不同的类加载器加载的,那么该class对应的Java类型则不一致,其实例化出的对象(静态)也肯定不一致;那么一个Tomcat服务器要同时运行多个Web应用,如何保证其加载进内存的jar包正常执行,其需要具备以下四点:

  1. 隔离性:Web应用类库相互隔离,避免各个服务之间的类库或jar包相互影响;
  2. 灵活性:每个Web应用的启动和关闭,对应的就是类的加载和销毁是独立灵活的。不会对其他Web应用产生影响;
  3. 性能:保证Tomcat的执行性能,例如加载新的Web服务和卸载旧的服务时,应稳定且快速完成;
  4. 共享:Tomcat通过Common类加载器实现了Jar包在应用服务器以及Web应用之间的共享;通过Shared类加载器实现了Jar包在Web应用之间的共享;
Tomcat的类加载器模型

在这里插入图片描述

  1. Common加载器

    Tomcat的顶层共用类加载器,其父类加载器是系统/应用类加载器。

    加载路径为common.loader,默认指向$CATALINA_HOME/lib下的包;

    加载:Tomcat应用服务器内部和Web应用均可见的类,例如Servlet规范,通用工具包;

  2. Catalina加载器

    Tomcat应用服务器的类加载器,其父类加载器是Common加载器。

    加载路径为server.loader,默认指向空,因此默认CatalinaLoader=CommonLoader;

    加载:加载Tomcat自身内部可见的类(例如Server,Service,Engine等),对Web应用不可见;目的是为了将Web应用和Tomcat服务器解耦;

  3. Shared加载器

    是Web应用的父加载器,其父类加载器是Common加载器。

    加载路径为shared.loader,默认指向空,因此默认SharedLoader=CommonLoader;

    加载:加载Web应用中共享的类,这些类Tomcat内部不会依赖;

  4. Web应用

    以Shared架子啊其为父类,加载/WEB-INF/classes目录下的未压缩的Class和资源文件以及/WEB-INF/lib目录下的Jar包。

    该类加载器只对当前Web应用可见,对其他Web应用均不可见;

以上类加载器配置均在conf/catalina.properties文件中

common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
# 默认为空
server.loader=
# 默认为空
shared.loader=

默认下的Tomcat类加载器

在这里插入图片描述

Tomcat类加载器源码
流程图

在这里插入图片描述

1. Bootstrap.init()

Tomcat启动的时候,调用引导类init()方法,第一步就是初始化类加载器;请看方法2

// Bootstrap.class
public void init() throws Exception {
    // 初始化类加载器
    // commonLoader: 加载catalina.properties配置文件中common.loader=>加载工作/安装目录下的lib文件
    // catalinaLoader: server.loader,默认为空,因此是commonLoader
    // sharedLoader: shared.loader,默认为空,因此是commonLoader
    initClassLoaders();
    // ...
}
2. Bootstrap.initClassLoaders()

在这个方法中,很明显可以看出,我们通过调用createClassLoader()方法,并指定配置文件的键值,去创建类加载器,还能指定其父类加载器;具体看方法3

// Bootstrap.class
private void initClassLoaders() {
    try {
        commonLoader = createClassLoader("common", null);
        if (commonLoader == null) {
            // no config file, default to this loader - we might be in a 'single' env.
            commonLoader = this.getClass().getClassLoader();
        }
        // 默认未配置,则返回commonLoader
        catalinaLoader = createClassLoader("server", commonLoader);
        // 默认未配置,则返回commonLoader
        sharedLoader = createClassLoader("shared", commonLoader);
    } catch (Throwable t) {
        handleThrowable(t);
        log.error("Class loader creation threw exception", t);
        System.exit(1);
    }
}
3. Bootstrap.createClassLoader()

这个函数就是将catalina.properties文件(上文已粘贴相应内容)中指定的类加载器配置读取,将扫描路径封装成Repository,并交由ClassLoaderFactory0createClassLoader()去处理;见方法4;

其中,因为默认server,shared没有配置,所以直接返回parent,及commonLoader;

// Bootstrap.class
private ClassLoader createClassLoader(String name, ClassLoader parent)
    throws Exception {
    // 加载工作和安装目录下lib路径的class
    String value = CatalinaProperties.getProperty(name + ".loader");
    // 这里就是为什么Common,Catalina,Shared为同一个类加载器的原因
    if ((value == null) || (value.equals(""))) {
        return parent;
    }
    // 前value: "${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
    // 后value: "D:\person_project\tomcat8.5/lib","D:\person_project\tomcat8.5/lib/*.jar","D:\person_project\tomcat8.5/lib","D:\person_project\tomcat8.5/lib/*.jar"
    // replace 将符号替换成绝对路径
    value = replace(value);

    List<Repository> repositories = new ArrayList<>();
    // 将value,拆成数组
    String[] repositoryPaths = getPaths(value);

    for (String repository : repositoryPaths) {
        // Check for a JAR URL repository
        try {
            @SuppressWarnings("unused")
            URL url = new URL(repository);
            repositories.add(new Repository(repository, RepositoryType.URL));
            continue;
        } catch (MalformedURLException e) {
            // Ignore
        }

        // Local repository
        if (repository.endsWith("*.jar")) {
            repository = repository.substring
                (0, repository.length() - "*.jar".length());
            repositories.add(new Repository(repository, RepositoryType.GLOB));
        } else if (repository.endsWith(".jar")) {
            repositories.add(new Repository(repository, RepositoryType.JAR));
        } else {
            repositories.add(new Repository(repository, RepositoryType.DIR));
        }
    }
    // 将目录封装成Repository,并根据所扫描的类型坐上标记(*.jar->glob, .jar->jar, other->dir)
    return ClassLoaderFactory.createClassLoader(repositories, parent);
}
4. ClassLoaderFactory.createClassLoader()

这里将传入的路径进行校验,将合法的文件转成URL传入AccessController.doPrivileged();

这里Tomcat为保证类扫描加载都能保证权限执行,使用AccessController保证代码可以顺利扫描加载,最终还是调用了new URLClassLoader()去创建类加载器;

// ClassLoaderFactory.class
public static ClassLoader createClassLoader(List<Repository> repositories,
                                            final ClassLoader parent)
    throws Exception {
    // Construct the "class path" for this class loader
    Set<URL> set = new LinkedHashSet<>();
    // 扫描路径校验 ...将repositories下的扫描文件生成URL放入set中
    // Construct the class loader itself
    final URL[] array = set.toArray(new URL[0]);
    if (log.isDebugEnabled()) {
        for (int i = 0; i < array.length; i++) {
            log.debug("  location " + i + " is " + array[i]);
        }
    }
    // 代码栈检查机制-中断了栈检查过程,使得后续原本没有权限的代码也可以正常执行
    return AccessController.doPrivileged(
        new PrivilegedAction<URLClassLoader>() {
            @Override
            public URLClassLoader run() {
                if (parent == null) {
                    return new URLClassLoader(array);
                } else {
                    return new URLClassLoader(array, parent);
                }
            }
        });
}
// native 方法
public static native <T> T doPrivileged(PrivilegedAction<T> action);

因此,默认Common,Catalina,Shared为同一个类加载器,都是CommonLoader;

Tomcat的Web应用类加载器

默认加载顺序(delegate=false):

  1. 从缓存中加载;
  2. 如果没有,则从JVM的Bootstrap类加载器(根加载器)加载;
  3. 如果没有,则从当前类加载器加载(按照WEB-INF/classes、WEB-INF/lib的顺序);
  4. 如果没有,则从父类加载器加载,由于父类加载器采用默认的委派模式,所以加载顺序为System,Common,Shared;

开启delegate=true采用委派模式,加载顺序:

  1. 从缓存中加载;
  2. 如果没有,则从JVM的Bootstrap类加载器(根加载器)加载;
  3. 如果没有,则从父类加载器加载(System,Common,Shared);
  4. 如果没有,则从当前类加载器加载;
  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BugGuys

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值