我们在Tomcat启动过程(Tomcat源代码阅读系列之三)一文中已经知道Tomcat启动以后,会启动6条线程,他们分别如下:

[java] view plaincopy
  1. "ajp-bio-8009-AsyncTimeout" daemon prio=5 tid=7f8738afe000 nid=0x115ad6000 waiting on condition [115ad5000]
  2. "ajp-bio-8009-Acceptor-0" daemon prio=5 tid=7f8738b05800 nid=0x1159d3000 runnable [1159d2000]
  3. "http-bio-8080-AsyncTimeout" daemon prio=5 tid=7f8735acb800 nid=0x1158d0000 waiting on condition [1158cf000]
  4. "http-bio-8080-Acceptor-0" daemon prio=5 tid=7f8735acd000 nid=0x1157cd000 runnable [1157cc000]
  5. "ContainerBackgroundProcessor[StandardEngine[Catalina]]" daemon prio=5 tid=7f8732850800 nid=0x111203000 waiting on condition [111202000]
  6. "main" prio=5 tid=7f8735000800 nid=0x10843e000 runnable [10843c000]

在这里说明一下Tomcat用到的端口可能更容易理解一些。

8005      远程停服务端口
8080      为HTTP端口,
8443      为HTTPS端口
 8009     为AJP端口  APACHE能过AJP协议访问TOMCAT的8009端口。

其中5条是Dameon线程,而对于Java程序来说,当所有非Dameon程序都终止的时候,Jvm就会退出,因此要想终止Tomcat就只需要将main这一条非Dameon线程终止了即可,在org.apache.catalina.startup#Catalina的start()中被一个阻塞等待shutdown的await阻塞,所以main方法才没有退出。

Dameon线程又叫后台或者守护线程,它负责在程序运行期提供一种通用服务的线程,比如垃圾收集线程,非Dameon线程和Dameon线程的区别就在于当程序中所有的非Daemon线程都终止的时候,Jvm会杀死余下的Dameon线程,然后退出。

接下来,我们就来一步步的分析如何来让main线程终止,要想终止它,我们还是得从Tomcat的启动中来寻找答案,我们在分析Tomcat容器启动的时候,在Catalina#start中有一段代码,我们没有关注,接下来就来看看这段代码:

[java] view plaincopy
  1. <span style="font-size:18px;">public void start() {
  2. // ignore other code
  3. // Register shutdown hook
  4. // 1
  5. if (useShutdownHook) {
  6. if (shutdownHook == null) {
  7. shutdownHook = new CatalinaShutdownHook();
  8. }
  9. Runtime.getRuntime().addShutdownHook(shutdownHook);
  10. // If JULI is being used, disable JULI's shutdown hook since
  11. // shutdown hooks run in parallel and log messages may be lost
  12. // if JULI's hook completes before the CatalinaShutdownHook()
  13. LogManager logManager = LogManager.getLogManager();
  14. if (logManager instanceof ClassLoaderLogManager) {
  15. ((ClassLoaderLogManager) logManager).setUseShutdownHook(
  16. false);
  17. }
  18. }
  19. // 2
  20. if (await) {
  21. await();
  22. stop();
  23. }
  24. }</span>

这里就是Tomcat关闭流程的入口代码了。我在代码中标注了两处,我们首先来看标注1的地方。标注1的代码,我们用到了Jvm的shutdownHook机制。对于shutdownHook大家可以参考这个连接,我这里做一个简单的介绍,shutdown hook是一个已经初始化但是还没有启动的线程,当Jvm关闭的时候,它会启动并并发的运行所有已经注册过的shutdown hooks,多用于意外关闭Tomcat的时候。知道了这点,我们就来看看CatalinaShutdownHook线程做了什么事情?它的代码如下:

[java] view plaincopy
  1. <span style="font-size:18px;"><span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.startup.Catalina.CatalinaShutdownHook#run</span>
  2. </span>
[java] view plaincopy
  1. <span style="font-size:18px;">public void run() {
  2. try {
  3. if (getServer() != null) {
  4. Catalina.this.stop();
  5. }
  6. } catch (Throwable ex) {
  7. ExceptionUtils.handleThrowable(ex);
  8. log.error(sm.getString("catalina.shutdownHookFail"), ex);
  9. } finally {
  10. // If JULI is used, shut JULI down *after* the server shuts down
  11. // so log messages aren't lost
  12. LogManager logManager = LogManager.getLogManager();
  13. if (logManager instanceof ClassLoaderLogManager) {
  14. ((ClassLoaderLogManager) logManager).shutdown();
  15. }
  16. }
  17. }</span>

通过上面的代码,我们可以清楚的看到调用了Catalina的stop方法。而Catalina#stop方法最终又是调用了StandardServer#stop方法和destroy方法。通过这里,我们知道Tomcat利用了shutdown hook机制来在Jvm关闭的时候关闭各个组件。但是Jvm又是何时退出的呢?这就要来看标注为2的代码了。

接下来我们再来看看标注2的地方:在标注为2的代码中,首先判断了await属性是否为true,如果为true就调用await(),调用完以后,再调用stop方法,接下来我们就来看await()方法,而catalina的awit方法又调用了StandardServer#awit方法,它的代码如下:

[java] view plaincopy
  1. <span style="font-size:18px;"><span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.core.StandardServer#await</span>
  2. </span>
[java] view plaincopy
  1. <span style="font-size:18px;">public void await() {
  2. // Set up a server socket to wait on
  3. try {
  4. awaitSocket = new ServerSocket(port, 1,
  5. InetAddress.getByName(address));
  6. } catch (IOException e) {
  7. log.error("StandardServer.await: create[" + address
  8. + ":" + port
  9. + "]: ", e);
  10. return;
  11. }
  12. try {
  13. awaitThread = Thread.currentThread();
  14. // Loop waiting for a connection and a valid command
  15. while (!stopAwait) {
  16. ServerSocket serverSocket = awaitSocket;
  17. if (serverSocket == null) {
  18. break;
  19. }
  20. // Wait for the next connection
  21. Socket socket = null;
  22. StringBuilder command = new StringBuilder();
  23. try {
  24. InputStream stream;
  25. try {
  26. socket = serverSocket.accept();
  27. socket.setSoTimeout(10 * 1000);  // Ten seconds
  28. stream = socket.getInputStream();
  29. } catch (AccessControlException ace) {
  30. log.warn("StandardServer.accept security exception: "
  31. + ace.getMessage(), ace);
  32. continue;
  33. } catch (IOException e) {
  34. if (stopAwait) {
  35. // Wait was aborted with socket.close()
  36. break;
  37. }
  38. log.error("StandardServer.await: accept: ", e);
  39. break;
  40. }
  41. // Read a set of characters from the socket
  42. int expected = 1024; // Cut off to avoid DoS attack
  43. while (expected < shutdown.length()) {
  44. if (random == null)
  45. random = new Random();
  46. expected += (random.nextInt() % 1024);
  47. }
  48. while (expected > 0) {
  49. int ch = -1;
  50. try {
  51. ch = stream.read();
  52. } catch (IOException e) {
  53. log.warn("StandardServer.await: read: ", e);
  54. ch = -1;
  55. }
  56. if (ch < 32)  // Control character or EOF terminates loop
  57. break;
  58. command.append((char) ch);
  59. expected--;
  60. }
  61. } finally {
  62. // Close the socket now that we are done with it
  63. try {
  64. if (socket != null) {
  65. socket.close();
  66. }
  67. } catch (IOException e) {
  68. // Ignore
  69. }
  70. }
  71. // Match against our command string
  72. boolean match = command.toString().equals(shutdown);
  73. if (match) {
  74. log.info(sm.getString("standardServer.shutdownViaPort"));
  75. break;
  76. } else
  77. log.warn("StandardServer.await: Invalid command '"
  78. + command.toString() + "' received");
  79. }
  80. } finally {
  81. ServerSocket serverSocket = awaitSocket;
  82. awaitThread = null;
  83. awaitSocket = null;
  84. // Close the server socket and return
  85. if (serverSocket != null) {
  86. try {
  87. serverSocket.close();
  88. } catch (IOException e) {
  89. // Ignore
  90. }
  91. }
  92. }
  93. }</span>

通过上面的代码,我们可以看出在配置的端口上通过ServerSocket来监听一个请求的到来,如果请求的字符串和配置的字符串相同的话即跳出循环,这样的话就会运行stop方法,运行完了以后,main线程就退出了。

这里ServerSocket监听的端口,以及对比的字符串都是在conf/server.xml中配置的,缺省情况下,配置如下:,从这里可以看出监听端口为8005,关闭请求发送的字符串为SHUTDOWN.

看到这里,我们基本上已经清楚了Tomcat的关闭就是通过在8005端口,发送一个SHUTDOWN字符串。那么我们就来实验一下。首先启动Tomcat,然后在终端运行如下指令:

telnet

[plain] view plaincopy
  1. <span style="font-size:18px;">telnet 127.0.0.1 8005
  2. Trying 127.0.0.1...
  3. Connected to localhost.
  4. Escape character is '^]'.
  5. SHUTDOWN
  6. Connection closed by foreign host.</span>

运行telnet命令,并发送SHUTDOWN字符串以后,我们发现Tomcat就会退出await方法,然后执行stop方法最终停止。

但是一般情况下,我们停止tomcat都不会像上面那种方式来关闭,我们一般有两种方式来关闭:

  1. ps aux | grep java ,kill -9 
    对于这种方式,比较简单粗暴会直接干掉进程,不过这种简单粗暴的方式我也经常用。
  2. 运行shutdown.sh
    这种方式其实最终也是向server发送了一个SHUTDOWN字符串,我们接下来分析下第二种情况。
    查看shutdown.sh最终是调用了 catalina.sh,并传递了stop参数。查看catalina.sh脚本,最终其实是调用了org.apache.catalina.startup.Bootstrap#main,并传递参数stop.我们查看Bootstrap#main方法,发现会调用org.apache.catalina.startup.Bootstrap#stopServer,而Bootstrap#stopServer通过反射调用了org.apache.catalina.startup.Catalina#stopServer,我们来看看Catalina#stopServer方法,代码如下:
[java] view plaincopy
  1. <span style="font-size:18px;">public void stopServer(String[] arguments) {
  2. if (arguments != null) {
  3. arguments(arguments);
  4. }
  5. Server s = getServer();
  6. // 1
  7. if( s == null ) {
  8. // Create and execute our Digester
  9. Digester digester = createStopDigester();
  10. digester.setClassLoader(Thread.currentThread().getContextClassLoader());
  11. File file = configFile();
  12. FileInputStream fis = null;
  13. try {
  14. InputSource is =
  15. new InputSource(file.toURI().toURL().toString());
  16. fis = new FileInputStream(file);
  17. is.setByteStream(fis);
  18. digester.push(this);
  19. digester.parse(is);
  20. } catch (Exception e) {
  21. log.error("Catalina.stop: ", e);
  22. System.exit(1);
  23. } finally {
  24. if (fis != null) {
  25. try {
  26. fis.close();
  27. } catch (IOException e) {
  28. // Ignore
  29. }
  30. }
  31. }
  32. } else {
  33. // Server object already present. Must be running as a service
  34. try {
  35. s.stop();
  36. } catch (LifecycleException e) {
  37. log.error("Catalina.stop: ", e);
  38. }
  39. return;
  40. }
  41. // Stop the existing server
  42. s = getServer();
  43. // 2
  44. if (s.getPort()>0) {
  45. Socket socket = null;
  46. OutputStream stream = null;
  47. try {
  48. socket = new Socket(s.getAddress(), s.getPort());
  49. stream = socket.getOutputStream();
  50. String shutdown = s.getShutdown();
  51. for (int i = 0; i < shutdown.length(); i++) {
  52. stream.write(shutdown.charAt(i));
  53. }
  54. stream.flush();
  55. } catch (ConnectException ce) {
  56. log.error(sm.getString("catalina.stopServer.connectException",
  57. s.getAddress(),
  58. String.valueOf(s.getPort())));
  59. log.error("Catalina.stop: ", ce);
  60. System.exit(1);
  61. } catch (IOException e) {
  62. log.error("Catalina.stop: ", e);
  63. System.exit(1);
  64. } finally {
  65. if (stream != null) {
  66. try {
  67. stream.close();
  68. } catch (IOException e) {
  69. // Ignore
  70. }
  71. }
  72. if (socket != null) {
  73. try {
  74. socket.close();
  75. } catch (IOException e) {
  76. // Ignore
  77. }
  78. }
  79. }
  80. } else {
  81. log.error(sm.getString("catalina.stopServer"));
  82. System.exit(1);
  83. }
  84. }</span>

我们来分析一下标注的地方:

  1. 标注1的代码,此时因为是新开了一个进程,并且conf/server.xml还没有解析,因此s是NULL,通过Digester解析conf/server.xml,最终生成了未初始化的StandardServer对象。
  2. 标注2的代码,向standardServer.getPort返回的端口(其实这里面返回即是conf/server.xml中Server根节点配置的port和shutdown属性)发送了standardServer.getShutdown()返回的字符串,而默认情况下这个字符串就是SHUTDOWN.

分析到这里,我想大家已经清楚了Tomcat的关闭流程,我们再来总结一下: Tomcat启动的时候的主线程会在8005端口(默认配置,可以更改)上建立socket监听,当关闭的时候,最终其实就是新起了一个进程然后向Tomcat主线程监听的8005端口发送了一个SHUTDOWN字符串,这样主线程就会结束了,主线程结束了以后,因为其它的线程都是dameon线程,这样依赖Jvm就会退出了。

Tomcat源码解析四:Tomcat关闭过程相关推荐

  1. Tomcat源码解析五:Tomcat请求处理过程

    前面已经分析完了Tomcat的启动和关闭过程,本篇就来接着分析一下Tomcat中请求的处理过程. 在开始本文之前,咋们首先来看看一个Http请求处理的过程,一般情况下是浏览器发送http请求-> ...

  2. 源码解析 使用tomcat作为web容器时,用到的外观模式

    源码解析 使用tomcat作为web容器时,接收浏览器发送过来的请求, tomcat会将请求信息封装成ServletRequest对象, 如下图①处对象. 但是大家想想ServletRequest是一 ...

  3. Tomcat源码解析系列二:Tomcat总体架构

    Tomcat即是一个HTTP服务器,也是一个servlet容器,主要目的就是包装servlet,并对请求响应相应的servlet,纯servlet的web应用似乎很好理解Tomcat是如何装载serv ...

  4. dubbo(5) Dubbo源码解析之服务调用过程

    来源:https://juejin.im/post/5ca4a1286fb9a05e731fc042 Dubbo源码解析之服务调用过程 简介 在前面的文章中,我们分析了 Dubbo SPI.服务导出与 ...

  5. 基于8.0源码解析:startService 启动过程

    基于8.0源码解析:startService 启动过程 首先看一张startService的图,心里有个大概的预估,跟Activity启动流程比,Service的启动稍微简单点,并且我把Service ...

  6. Tomcat 源码解析一初识

      为什么我想研究Tomcat源码,我们现在都用的是SpringBoot开发项目,而SpringBoot对错Tomcat集成,导致现在基本上看不到Tomcat的身影了,但是Tomcat不存在吗?只要我 ...

  7. Tomcat源码解析一:下载源码与导入eclipse

    自从写web程序以来,web程序是如何在Tomcat中运行的一直困惑着我,不知道底层的运行机制是无法真正理解web的,所以就开始研究Tomcat源码,Tomcat是一个轻量级的java服务器,再结合& ...

  8. Tomcat源码解析:环境搭建

    下载源码 从github下载tomcat源码 git clone https://github.com/apache/tomcat.git 或者直接fork一份到自己仓库,以便后续添加注释,附上笔者自 ...

  9. 源码解析 React Hook 构建过程

    2018 年的 React Conf 上 Dan Abramov 正式对外介绍了React Hook,这是一种让函数组件支持状态和其他 React 特性的全新方式,并被官方解读为这是下一个 5 年 R ...

最新文章

  1. Hibernate(九)一对多双向关联映射
  2. mysql批量提交的优化
  3. L. Mod(预处理+分块)
  4. android--------Popupwindow的使用
  5. 【bzoj2330】 [SCOI2011]糖果
  6. 通用职责分配软件原则之9-受保护变量原则
  7. ArcGIS 代理产品价格以及折扣表、产品描述
  8. JavaScript:函数
  9. 设计模式:高性能IO之Reactor模式
  10. 可临摹素材,分层可编辑一步一步教你,肯定能把表单做好
  11. idea之springboot端口被占用/跳转到login
  12. 曾经的 Java IDE 王者 Eclipse 真的没落了?21 款插件让它强大起来!
  13. 9. grouped product
  14. 大白菜U盘启动盘手动去除捆绑第三方赞助软件
  15. MATLAB一元三次求极值,MATLAB实现一元三次方程求解/盛金公式
  16. 【Java基础】Java网络编程基础
  17. 计算机三级路由器配置例题,计算机三级网络技术(7):路由器配置及使用(上)...
  18. 计算机检索自考,计算机信息检索02139自考资料(25页)-原创力文档
  19. 【青少年编程】【三级】接苹果
  20. 基于AI的自动化处理

热门文章

  1. flutter 动画json_Flutter 50: 图解动画小插曲之 Lottie 动画
  2. docker容器的本地局域网yum源优化
  3. shell脚本详解(七)——正则表达式、sort、uniq、tr
  4. hyperion高光谱参数_收藏!光纤光谱仪在激光领域的典型应用
  5. 80核处理器_标压版锐龙处理器更香!联想小新Pro 13轻薄笔记本评测
  6. python 最小硬币数_Python之动态规划(最少硬币数找零)
  7. python写水仙花的作文_python自动打开浏览器下载zip,并且提取内容写入excel
  8. ubuntu安装pr_在Ubuntu 16.04服务器上安装Zabbix 3.2
  9. rf调用的python函数报错_Robot Framework(15)- 扩展关键字
  10. java 导出excel二维表,如何轻松将EXCEL二维统计表转为数据清单?