5、tomcat的停止

Sep 16, 2016


前言

前面我们已经经历了tomcat的启动和请求接收处理,本节我们来看看tomcat的停止过程。

Bootstrap

tomcat的停止仍然是从Bootstrap开始的,当我们输入的命令为stop的时候,tomcat会开始进入停止的流程,源码如下:

else if (command.equals("stop")) {
    daemon.stopServer(args);
}

在tomcat启动的章节中,我们已经知道daemon其实就是Bootstrap的实例化对象,这里调用的是stopServer方法,源码如下:

public void stopServer(String[] arguments)
    throws Exception {

    Object param[];
    Class<?> paramTypes[];
    if (arguments==null || arguments.length==0) {
        paramTypes = null;
        param = null;
    } else {
        paramTypes = new Class[1];
        paramTypes[0] = arguments.getClass();
        param = new Object[1];
        param[0] = arguments;
    }
    Method method =
        catalinaDaemon.getClass().getMethod("stopServer", paramTypes);
    method.invoke(catalinaDaemon, param);

}

这里我们可以看到,依然是代理了Catalina类的stopServer方法。

Catalina

那么我们来看一下真正的结束方法:

public void stopServer(String[] arguments) {

    if (arguments != null) {
        arguments(arguments);
    }

    Server s = getServer();
    if (s == null) {
        // ... 省略不影响理解的代码
    } else {
        try {
            //【1】如果server不为null,则停止服务
            s.stop();
        } catch (LifecycleException e) {
            log.error("Catalina.stop: ", e);
        }
        return;
    }
    //【2】结束服务并关闭应用
    s = getServer();
    if (s.getPort()>0) {
        try (Socket socket = new Socket(s.getAddress(), s.getPort());
                OutputStream stream = socket.getOutputStream()) {
            String shutdown = s.getShutdown();
            for (int i = 0; i < shutdown.length(); i++) {
                stream.write(shutdown.charAt(i));
            }
            stream.flush();
        } catch (ConnectException ce) {
            log.error(sm.getString("catalina.stopServer.connectException",
                                   s.getAddress(),
                                   String.valueOf(s.getPort())));
            log.error("Catalina.stop: ", ce);
            System.exit(1);
        } catch (IOException e) {
            log.error("Catalina.stop: ", e);
            System.exit(1);
        }
    } else {
        log.error(sm.getString("catalina.stopServer"));
        System.exit(1);
    }
}

这里我们可以看到,有两个分支结束应用,分支【2】很简单,主要是通过socket调用tomcat的关闭钩子来关闭tomcat,而分支【1】则是直接通过stop方法来结束应用,我们来看一下这个方法:

public void stop() {
    try {
        // 【1】移除停止tomcat的钩子
        if (useShutdownHook) {
            Runtime.getRuntime().removeShutdownHook(shutdownHook);

            LogManager logManager = LogManager.getLogManager();
            if (logManager instanceof ClassLoaderLogManager) {
                ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                        true);
            }
        }
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
    }

    // 【2】停止应用
    try {
        Server s = getServer();
        LifecycleState state = s.getState();
        if (LifecycleState.STOPPING_PREP.compareTo(state) <= 0
                && LifecycleState.DESTROYED.compareTo(state) >= 0) {
        } else {
            s.stop();
            s.destroy();
        }
    } catch (LifecycleException e) {
        log.error("Catalina.stop", e);
    }

}

这里可以看到,首先移除了启动的时候添加的停止tomcat的钩子,这里主要是为了防止多次停止,之后还是判断tomcat目前的状态,是否正在停止的过程,如果不是的话,会直接调用server的stop方法和destroy方法,先停止服务,最后销毁服务,完成一个完整的生命周期。

Server

Tomcat的停止过程和启动过程相似,从顶层容器Server开始逐层往下调用stop方法,我们来看一下server的stop

@Override
protected void stopInternal() throws LifecycleException {

    setState(LifecycleState.STOPPING);
    fireLifecycleEvent(CONFIGURE_STOP_EVENT, null);

    // 【1】停止service
    for (int i = 0; i < services.length; i++) {
        services[i].stop();
    }
    // 【2】停止全局命名资源
    globalNamingResources.stop();
    // 【3】等待停止完成
    stopAwait();
}

这里还是先停止所有的service对象,然后停止全局资源,之后就阻塞线程等待全部组件停止完成。

这里我们主要看service的停止过程:

protected void stopInternal() throws LifecycleException {

    // 【1】暂停所有连接器
    synchronized (connectorsLock) {
        for (Connector connector: connectors) {
            try {
                connector.pause();
            } catch (Exception e) {
                log.error(sm.getString(
                        "standardService.connector.pauseFailed",
                        connector), e);
            }
        }
    }

    if(log.isInfoEnabled())
        log.info(sm.getString("standardService.stop.name", this.name));
    setState(LifecycleState.STOPPING);

    // 【2】停止Container
    if (container != null) {
        synchronized (container) {
            container.stop();
        }
    }

    // 【3】停止连接器
    synchronized (connectorsLock) {
        for (Connector connector: connectors) {
            if (!LifecycleState.STARTED.equals(
                    connector.getState())) {
                // Connectors only need stopping if they are currently
                // started. They may have failed to start or may have been
                // stopped (e.g. via a JMX call)
                continue;
            }
            try {
                connector.stop();
            } catch (Exception e) {
                log.error(sm.getString(
                        "standardService.connector.stopFailed",
                        connector), e);
            }
        }
    }

    // 【4】停止全部映射监听器
    if (mapperListener.getState() != LifecycleState.INITIALIZED) {
        mapperListener.stop();
    }
    // 【5】停止线程池
    synchronized (executors) {
        for (Executor executor: executors) {
            executor.stop();
        }
    }
}

service的停止过程比较麻烦,需要停止4个组件:

  • 连接器
  • 容器(Engine)
  • 监听器
  • 线程池

在停止过程,我们注意到,对于连接器,是先暂停了,然后等待容器停止完成之后才停止,这里先暂停连接器是停止再接收请求,等待容器停止是为了保证已经接收的请求全部处理完成,最后才能停止连接器。

这里我们不再对每个组件的停止详细介绍了,在tomcat启动的过程中,我们知道,连接器代理了协议处理器(ProtocolHandler),而协议处理器代理了协议入口org.apache.tomcat.util.net.AbstractEndpoint,这里我们还是以HTTP协议为例,来看一下org.apache.tomcat.util.net.NioEndpoint中的stopInternal的实现:

@Override
public void stopInternal() {
    //【1】释放弹簧连接
    releaseConnectionLatch();
    if (!paused) {
        pause();
    }
    if (running) {
        running = false;
        // 【2】释放接收器线程
        unlockAccept();
        // 【3】清除所有辨识线程
        for (int i=0; pollers!=null && i<pollers.length; i++) {
            if (pollers[i]==null) continue;
            pollers[i].destroy();
            pollers[i] = null;
        }
        try {
            stopLatch.await(selectorTimeout + 100, TimeUnit.MILLISECONDS);
        } catch (InterruptedException ignore) {
        }
        //【4】停止线程池
        shutdownExecutor();
        //【5】清除所有相关对象
        eventCache.clear();
        nioChannels.clear();
        processorCache.clear();
    }

}

停止的过程比较简单,主要是停止由自己启动的线程和对象即可。

所有的停止完成后,基本上会按照相同的流程在执行一次destory

这个过程就不同全部写出来了,并没有什么复杂的逻辑,请同学们自己花点时间了解一下即可。

总结

tomcat停止的过程和启动的过程类似,都是从上往下逐层调用生命周期中的方法,并且清理每个组件自己生成的对象和内部持有的组件即可。