Tomcat源码分析-类加载器

ClassLoaderFactory.java
public static ClassLoader createClassLoader(List<Repository> repositories, final ClassLoader parent)
        throws Exception {
    Set<URL> set = new LinkedHashSet<>();
    if (repositories != null) {
        for (Repository repository : repositories)  {
            // 对不同类型的 Repository 对象进行处理,将路径转换为URL类型
            // 因为 URL 类型带有明显的协议,比如jar:xxx、file:xxx
        }
    }
    // 将对应的路径组装成 URL
    final URL[] array = set.toArray(new URL[set.size()]);
    // 在创建 URLClassLoader 需要考虑到 AccessController 的影响
    return AccessController.doPrivileged(
        new PrivilegedAction<URLClassLoader>() {
            public URLClassLoader run() {
                if (parent == null) 
                    return new URLClassLoader(array);
                else 
                    return new URLClassLoader(array, parent);
            }
        });
}

private static URL buildClassLoaderUrl(File file) throws MalformedURLException {
    String fileUrlString = file.toURI().toString();
    fileUrlString = fileUrlString.replaceAll("!/", "%21/"); // 转换成URL编码
    return new URL(fileUrlString);

OK,前面介绍了 tomcat 创建类加载器的过程,接下来我们看下 tomcat 类加载器的具体应用场景

WebappClassLoader

在前面,我们介绍了 tomcat 类加载器的设计,每个 webapp 使用单独的类加载器完成我们开发的 webapp 应用程序的类加载,而每一个 webapp 对应一个 WebappClassLoader。tomcat7 默认使用 WebappClassLoader 类加载器,而 tomcat8 默认使用 ParallelWebappClassLoader,支持并行加载类的特性,这也算是 tomcat8 做的一些优化吧,而实际上也是利用 jdk 的功能,需要同时满足以下两点才支持并行加载类,并且一旦注册了并行加载的能力,就不能回退了

1、 没有创建调用者的实例
2、 调用者的所有超类(除了类对象)都是并行注册的

基于上面两点,因此,ParallelWebappClassLoader 在 static 代码块中注册并行加载机制,而它的父类 URLClassLoader 父类也是具有并行能力的,关键代码如下所示:

public class ParallelWebappClassLoader extends WebappClassLoaderBase {
    static {
        boolean result = ClassLoader.registerAsParallelCapable();
        if (!result) {
            log.warn(sm.getString("webappClassLoaderParallel.registrationFailed"));
        }
    }
    // 省略无关代码...

WebappClassLoader 的类图如下所示,其中 WebappClassLoaderBase 实现了主要的逻辑,并且继承了 Lifecycle,在 tomcat 组件启动、关闭时会完成资源的加载、卸载操作,例如在 start 过程会读取我们熟悉的 /WEB-INF/classes/WEB-INF/lib 资源,并且记录每个 jar 包的时间戳方便重载 jar 包;而在组件 stop 的时候,会清理已经加载的资源;destory 时会显式地触发 URLClassLoader.close()。这个 Lifecycle 真是无处不在啊

单独的类加载器是无法获取 webapp 的资源信息的,因此 tomcat 引入了 WebappLoader,便于访问 Context 组件的信息,同时为 Context 提供类加载的能力支持,下面我们分析下 WebappLoader 的底层实现

WebappLoader

我们先来看看 WebappLoader 几个重要的属性,内部持有 Context 组件,并且有个我们熟悉的 reloadable 参数,如果设为 true,则会开启类的热加载机制

public class WebappLoader extends LifecycleMBeanBase
    implements Loader, PropertyChangeListener {
    private WebappClassLoaderBase classLoader = null;   // 默认使用ParallelWebappClassLoader
    private Context context = null;
    private String loaderClass = ParallelWebappClassLoader.class.getName();
    private ClassLoader parentClassLoader = null;       // 父加载器,默认为 catalina 类加载器
    private boolean reloadable = false;                 // 是否支持热加载类
    private String classpath = null;

在 tomcat 中,每个 webapp 对应一个 StandardContext,在 start 过程便会实例化 WebappLoader,并且调用其 start 方法完成初始化,包括创建 ParallelWebappClassLoader 实例,然后,还会启动 Context 的子容器。注意,这两个过程,都会将线程上下文类加载器指定为 ParallelWebappClassLoader 类加载器,在完成 webapp 相关的类加载之后,又将线程上下文类加载器设置为 catalina 类加载器。Context 容器的启动过程,这里便不再重复了,感兴趣的童鞋请查看前面的博文 webapp源码分析

StandardContext.java

protected synchronized void startInternal() throws LifecycleException {
    // 实例化 Loader 实例,它是 tomcat 对于 ClassLoader 的封装,用于支持在运行期间热加载 class 
    if (getLoader() == null) {
        WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
        webappLoader.setDelegate(getDelegate());
        setLoader(webappLoader);    // 使用了读写锁控制并发问题
    }

    // 将 Loader 中的 ParallelWebappClassLoader 绑定到当前线程中,并返回 catalian 类加载器
    ClassLoader oldCCL = bindThread();

    try {
        if (ok) {
            // 如果 Loader 是 Lifecycle 实现类,则启动该 Loader
            Loader loader = getLoader();
            if (loader instanceof Lifecycle) {
                ((Lifecycle) loader).start();
            }

            // 设置 ClassLoader 的各种属性
            setClassLoaderProperty("clearReferencesRmiTargets", getClearReferencesRmiTargets());

            // 省略……
            // 解除线程上下文类加载器绑定
            unbindThread(oldCCL);
            oldCCL = bindThread();

            // 发出 CONFIGURE_START_EVENT 事件,ContextConfig 会处理该事件,主要目的是加载 Context 的子容器
            fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

            // 启动子容器
            for (Container child : findChildren()) {
                if (!child.getState().isAvailable()) {
                    child.start();
                }
            }
        }
    } finally {
        // Unbinding thread
        unbindThread(oldCCL);
    }

而 WebappLoader 在 stop 的时候,会销毁 WebappClassLoader,并且进行回收,促使 jvm 卸载已加载的类

WebappLoader.java

@Override
protected void stopInternal() throws LifecycleException {
    // 省略不相关代码...
    if (classLoader != null) {
        try {
            classLoader.stop();
        } finally {
            classLoader.destroy();
        }
    }
    classLoader = null; // help gc

Hotswap

我们可以为 Context 组件指定 reloadable 属性,如果设为 true,tomcat便会启用 Hotswap,定期扫描类文件的变动,如果有变动,则重启 webapp 从而达到 Hotswap 的目的。

这个参数由 Context 指定的,但是会通过 WebappLoader#setContext(Context context) 方法调用,从而传递给 WebappLoader

WebappLoader 提供了后台定时任务的方法,Context 容器会间隔性地进行调用,它用于监听 classjar 等文件的变更,一旦有变动,便会对 Context 容器进行 reload 操作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Java面试大全

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

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

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

打赏作者

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

抵扣说明:

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

余额充值