Tomcat源码分析—停止过程

Tomcat源码分析—停止过程

这篇文章是我在2011年时写的,现转到我自己的博客上

当Tomcat执行完后,会注册一个钩子线程,之后主线程main会启动一个ServerSocket,监听8005的数据。
这两个方式都是用来做关闭处理的,钩子线程会调用Catalina#stop(),由Catalina负责关闭整个容器。
当执行shutdown.bat命令后会发起一个socket连接,连接器到本地的8005端口,这样原本处于等待的BIO连接器就激活了,会转而继续执行后续的方法,后续的方法就是stop()方法,这样也就关闭了容器。
使用socket监听的好处是可以让关闭的方式多种多样,只要接受到一个关闭命令就可以了,这个关闭命令可能是本地发出的,也可能是远端发出的。

关闭过程我将它分三个部分介绍,暂停连接器,关闭容器,关闭线程池和连接器
第一和第三部分很简单,主要是第二部分复杂,首先来看第一部分
暂停连接器时序图:

如果是通过钩子线程关闭的就是从CatalinaShutdownHook开始调用的,由它调用Catalina#stop(),为简单起见,以后都是从Catalina#stop()开始。首先是调用Server#stop(),然后是Service#stop(),Service会先暂停所有的连接器,然后关闭容器,再关闭线程池和连接器,这里为简单省略了Service。
暂停连接器的过程是一个循环,因为可能有多个连接器,关闭都是调用Connector#stop(),这个类会被调用多次。
1.如果是BIO,就调用Http11Protocol#pause(),由它再调用JIoEndpoint#pause(),这个方法会调用unlockAccept(),这个方法
又是做什么的?它的目的是新创建一个socket去连接本地的BIO端口,这个连接的目的是让原本处于监听阻塞的BIO连接器激活。
2.如果是NIO,就调用Http11NioProtocol#pause()  ,再由它调用NioEndpoint#pause(),同样它也会启动一个socket激活本地
的NIO连接器。
3.如果是AJP,就会调用JkCoyoteHandler#pause() ,再由它调用JkMain#pause(),然后再调用ChannelSocket#stop(),此时也
会创建一个socket连接本地的AJP连接器端口。

 

暂停过程就这样结束了,很简单吧,下面是关闭容器的过程
关闭容器的时序图:

还是从Server开始,调用Serice,然后调用引擎的stop(),引擎会调用父类ContainerBase#stop(),此时会将后台线程关闭,调用threadStop()简单的将线程终止了,不过这样终止可能会导致正在检查的工作不能很好的进行了,也就是说关闭的时候后台线程不能保证完整的执行。ContainerBase会调用引擎的子容器主机的关闭。
下面来复习一下ContainerBase#stop()方法:(去掉了注释)

    public synchronized void stop() throws LifecycleException {
        if (!started) {
            if(log.isInfoEnabled())
                log.info(sm.getString("containerBase.notStarted", logName()));
            return;
        }
        lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
        threadStop();
        lifecycle.fireLifecycleEvent(STOP_EVENT, null);
        started = false;
        if (pipeline instanceof Lifecycle) {
            ((Lifecycle) pipeline).stop();
        }
        Container children[] = findChildren();
        for (int i = 0; i < children.length; i++) {
            if (children[i] instanceof Lifecycle)
                ((Lifecycle) children[i]).stop();
        }
        children = findChildren();
        for (int i = 0; i < children.length; i++) {
            removeChild(children[i]);
        }

        if ((resources != null) && (resources instanceof Lifecycle)) {
            ((Lifecycle) resources).stop();
        }
        if ((realm != null) && (realm instanceof Lifecycle)) {
            ((Lifecycle) realm).stop();
        }
        if ((cluster != null) && (cluster instanceof Lifecycle)) {
            ((Lifecycle) cluster).stop();
        }
        if ((manager != null) && (manager instanceof Lifecycle)) {
            ((Lifecycle) manager).stop();
        }
        if ((logger != null) && (logger instanceof Lifecycle)) {
            ((Lifecycle) logger).stop();
        }
        if ((loader != null) && (loader instanceof Lifecycle)) {
            ((Lifecycle) loader).stop();
        }
        lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
    }
                                                                                   

stop()方法会关闭后台线程,触发关闭的事件监听器,关闭pipeline,关闭所有的子容器,删除所有子容器,关闭和当前容器相关联的Resource,Realm,Cluster,Manager,Logger,Loader。这个stop()方法就是关闭的核心,掌握了这个stop()原理,关闭流程就很清楚了。
调用StandardHost#stop()会触发HostConfig#stop(),由它负责清理所有部署资源,首先调用自身的undeployApps(),这会触发调用StandardHost#removeChild(),然后由父类ContainerBase负责删除它的子容器,也就是StandardContext,先调用它的stop()进行关闭,
1.这会调用到StandardWrapper#stop(),包装类会卸载相应的Servlet,也就是调用自定义Servlet的destroy()方法,包装还会调用父类ContainerBase#stop(),不过包装没有关联什么组件,所以这里没有什么关闭操作。
2.StandardContext接着会调用filterStop(),将ApplicationFilterConfig相关的资源全部清除掉。
3.之后是调用StandardManager#stop(),清除session。Manager会调用StandardSessioin#passivate(),由StandardSession负
调用自定义HttpSessionActivationListener#sessionWillPassivate(),执行session钝化方法。接着StandardSession执行expire()
将自身从ManagerBase中清楚,当然这也会触发自定义的Session监听器。
4.调用完Manager之后上下文会调用listenerStop(),关闭所有的监听器,这个方法会触发自定义
ServletContextListener#contextDestroyed()
5.调用ContextConfig#stop(),它负责删除所有在web.xml定义的属性列表
6.调用ApplicationContext#clearAttributes()清除相关属性
7.调用自身的resourcesStop(),清理相关资源
8.调用WebappLoader#stop(),删除所有上下文的属性,所以会调用到ApplicationContextFacade,这只是一个门面类,再调用到
ApplicationContext#removeAttribute,最终会触发自定义ServletContextAttributeListener#attributeRemoved()
9.上下文调用自己的resetContext(),将FileDirContext的一些属性清除。

好了,执行到这里,上下文和它的子容器都执行完了,返回给主机,此时主机也已经删除了所有的上下文,所有继续返回到引擎,引擎会调用UserDatabaseRealm#stop(),不过这里也没做什么。引擎执行完后,返回给Service,到目前为止,容器就执行完了。当然我们省略了一些东西,就是解注册MBean,这个很简单,也就略过了。

 

 

最后是停止线程池和连接器,这部分比较简单,先看一下时序图:

Service会循环调用Connector#stop()关闭所有连接器
1.如果是BIO,调用Http11Protocol#destroy() ,再由它调用JIoEndpoint#destroy(),这里会调用unlockAccept(),
关闭ServerSocket。
2.如果是NIO,调用Http11NioProtocol#destroy(),再由它调用NioEndpoint##destroy(),这里会调用unlockAccept()
关闭ServerSocketChannel
3.如果是AJP,会调用JkCoyoteHandler#destroy()  ,再调用JkMain#stop(),由JkMain调用ChannelSocket#stop(),
ChannelSocket会先调用ThreadPool#shutdown(),然后调用unLockSocket(),关闭ServerSocket

连接器调用完后就是关闭线程池了,线程池可能有多个,所以也是循环关闭,调用很简单,直接调用StandardThreadExecutor#stop(),再由它调用ThreadPoolExecutor#shutdown()

现在容器,连接器,线程池都关闭完了,Service返回到Server,在Server中调用stopAwait()关闭等待监听8005的ServerSocket。
还会触发和Server关联的6个监听器。
这一切做完后,容器的关闭过程也就结束了。

4 次阅读

发表评论

电子邮件地址不会被公开。