本文首发于 vivo互联网技术 微信公众号
链接:https://mp.weixin.qq.com/s/ZqkmoAR4JEYr0x0Suoq7QQ
作者:马运杰

本文通过阅读Tomcat启动和关闭流程的源码,深入分析不同的Tomcat关闭方式背后的原理,让开发人员能够了解在使用不同的关闭方式时需要注意的点,避免因JVM进程异常退出导致的各种非预见性错误。

一、 Tomcat的启动过程

要了解Tomcat关闭的原理,首先需要关注下Tomcat是如何启动的。这里我们简单介绍下。

Tomcat启动的入口是Bootstrap类中的main方法,而后根据server.xml中的配置,对Server、Service、Enigin、Connector、Host、Context等组件进行初始化,之后便是启动这些组件。我们重点来看下启动之后,Tomcat做了哪些工作。

在Tomcat的各组件启动完毕之后,main主线程会进入Catalina.out的await()方法,而此方法又是主要调用了Server组件的await()方法,从名字便可以看出,这个方法的目的是为了阻塞当前线程(main主线程)。

分析await的源码(源码比较长,这里截取了部分,全部的可以自行拉取Tomcat源码进行阅读)。

(StandardServer.await())

我们发现await()方法主要是根据server.xml中Server节点port属性的设置做了以下几种工作:

  • port为-2时,函数直接退出,此时主线程不会阻塞。
  • port为-1时,将等待线程设置为当前线程,并且进入while循环,直到stopAwait标志位置为true
  • port为其他时,则会新建一个socket服务端,该socket绑定了当前服务器的ip以及port端口,随后设置等待线程为当前线程,并且socket进入阻塞监听状态,直到socket监听到server.xml中预置的关闭字符串(默认是"SHUTDOWN")

在主线程退出等待后,就会进入Tomcat的关闭流程,进行各个组件的stop和destroy操作。从上述分析可以看出,要想停止Tomcat,就是要中断main主线程的等待状态。

下图为Tomcat的整个生命周期。

(Tomcat生命周期)

二、常见的关闭Tomcat的方式

1、我们下载的Tomcat压缩包的bin目录下,有一个由官方提供的脚本(shutdown.sh),可以用来结束Tomcat进程。

2、服务器上,我们还可以利用kill -x命令来结束Tomcat进程。

3、此外,代码中的System.exit()以及OOM等异常情况的发生,也会导致Tomcat进程的关闭,但是这两者都不是正常的运维手段,在此我们不做分析。

三、shutdown脚本

1、shutdown.sh的原理

查看分析官方的shutdown.sh脚本以及catalina.sh脚本,发现这两个脚本最终是在调用Bootstrap类的main方法,和启动Tomcat时调用的是同一个方法,差异在于传入了"stop"作为main方法的参数,而传入了该参数的main方法,会调用Catalina类的stopServer()方法。在此我们抹去不需要关注的代码,可以把整个stopServer()方法简化为如下4步:

其主要做了两件事:

  • 初始化Server组件,和Tomcat启动时类似,这一步主要是解析server.xml文件,然后根据server.xml中的属性初始化Tomcat组件的成员变量,这里主要关注Server组件的几个成员变量:port、address、shutdown,默认值分别为8005、127.0.0.1、SHUTDOWN等,需要和启动时读取的server.xml保持一致。
  • 往address port所监听的Socket端口发生“SHUTDOWN”字符串。

至此,显而易见的,这对应了第一小节中的第三种阻塞情况,"SHUTDOWN"字符串让main主线程结束了等待状态,并在接下来通过调用各组件的stop()和destroy()方法进行资源的释放。

2、shutdown脚本的缺点

虽然shutdown脚本是由Tomcat官方出品,但是其在实际应用中并不广泛,主要是由于下面两个缺点:

  • 从上述原理就可以分析出,shutdown脚本是基于启动时监听了相应的端口,这就允许任意人员,只要能够发送"SHUTDOWN"字符串到相应的端口,就可以对Tomcat进程进行关闭,这对于生产环境是相当危险的。所以一般生产环境会将Server的port属性设置为-1
  • shutdown脚本只是结束了main主线程的等待状态,让其正常的走下去。我们知道,JVM中的线程分为守护线程和用户线程两种类型,守护线程会在所有用户线程结束后,自动回收,进而导致JVM进程的退出。main主线程是一个用户线程,但是随着程序越来越复杂,可能会出现很多其他的用户线程。比如我们平常开发过程中,常用的创建线程池的操作Executors.newFixedThreadPool(n) 便会创建n个用户线程,这些线程在main主线程退出后,并不会自动回收,从而阻止了JVM的正常退出。所以经常会发生调用了shutdown脚本,但是Tomcat进程无法退出的场景。

四、kill -x

1、kill -9 or kill -15

Linux中的kill -x操作是向目标进程发送对应的信号量。可以用kill -l命令查看每个数值所代表的信号量的值。

(kill信号量)

这里面,我们经常会使用kill-9这一命令,kill -9会立即强制结束当前进程,这个操作既方便,但同时也极具破坏性。在实际的环境中,我们可能有在running的任务,如果此时程序被强制关闭,便会导致当前任务数据的丢失,特别是时间特别长的任务,极有可能造成前功尽弃的局面。同时,如果程序设计不当,没有相应的幂等操作,还有可能会造成实际环境中数据缺失或者脏数据的产生,对生产环境造成致命的问题。

相比kill -9, kill -15(15只是一个例子,Linux中还有其他的中断信号)会相对优雅很多。kill -15是向进程发送一个TERM的中断信号量,在JVM接收到该信号量后,会响应中断,进而结束当前进程。而这一操作能够优雅关闭Tomcat的原因在于,JVM在结束当前进程前,会启动一系列名为shutdownhook(关闭钩子)的线程,而这些线程就会成为我们进行风险控制的工具。接下来我们首先看看Tomcat中的关闭钩子。

2、shutdownhook关闭钩子

Tomcat的关闭钩子的定义是在Catalina类中,有一个名为CatalinaShutdownHook内部类,继承了Thread类。跟着这个线程类中的run()方法往下看,其调用了Catalina的stop()方法,而此处stop方法,除了正常去停止各组件外,还会去中断并快速结束main主线程(如果主线程还存在的话),最后再调用各组件的destroy()方法进行资源释放。

(Tomcat中的shutdownhook)

除了Tomcat会使用关闭钩子外,很多中间件也会使用到这一非常重要的功能。

我们在平常的开发过程中也可以使用关闭钩子,可以在程序启动或者运行阶段通过调用Runtime.getRuntime().addShutdownHook(shutdownHook)方法进行钩子的添加,但要注意的是,需要在关闭的流程中加入移除钩子的代码。

Spring中当然也有关闭钩子的应用,并且还为我们使用关闭钩子提供了更为友好的编程体验。

在Spring中,关闭钩子是在AbstractApplicationContext.registerShutdownHook()方法中添加的(下图中的代码),而其关闭钩子的run方法则会调用destroyBeans()方法,其对所有继承了DisposableBean接口的类调用其destroy()方法。

读到这里我们就明白了,在平时开发时,如果有使用关闭钩子的需求,可以通过继承DisposableBean,并实现其destroy(),很方便的来达到我们回收资源,打扫战场的目的。

3、shutdownhook的使用注意点

shutdownhook在使用中也并不是可以随意乱用的,需要注意以下几点:

  • shutdownhook的调用是不保证顺序的
  • shutdownhook是JVM结束前调用的线程,所以该线程中的方法应尽量短,并且保证不能发生死锁的情况,否则也会阻止JVM的正常退出
  • shutdownhook中不能执行System.exit(),否则会导致虚拟机卡住,而不得不强行杀死进程

五、总结

本文对Tomcat两种常用关闭方式的原理进行了解读,从上述分析可以看出,用shutdown.sh脚本控制Tomcat关闭的方式存在权限的风险,并且也会由于开发中的线程操作导致Tomcat无法关闭,所以这种方法在实际应用中使用情况较少。

而kill -15则能够安全的杀死Tomcat进程,并且由于JVM shutdownhook的存在,我们可以对整个程序关闭时进行更强有力的控制,退出过程也更为优雅,所以使用较为广泛。

更多内容敬请关注 vivo 互联网技术 微信公众号

http://weixin.qq.com/r/KzgbAzzEqmHVrXhg9205 (二维码自动识别)

注:转载文章请先与微信号:labs2020 联系。

[转帖]Tomcat 优雅关闭之路的更多相关文章

  1. SpringBoot如何优雅关闭(SpringBoot2.3&Spring Boot2.2)

    SpringBoot如何优雅关闭(SpringBoot2.3&Spring Boot2.2) 优雅停止&暴力停止 暴力停止:像日常开发过程中,测试区或者本地开发时,我们并不会考虑项目关 ...

  2. 如何优雅关闭 Spring Boot 应用

    ## 前言 随着线上应用逐步采用 SpringBoot 构建,SpringBoot应用实例越来多,当线上某个应用需要升级部署时,常常简单粗暴地使用 kill 命令,这种停止应用的方式会让应用将所有处理 ...

  3. 优雅关闭服务下线(Jetty)

    在很多时候 kill -9 pid并不是很友好的方法,那样会将我们正在执行请求给断掉,同时eureka 中服务依旧是处于在线状态,这个时候我们可以使用官方提供的actuator来做优雅的关闭处理 - ...

  4. 如何在 Spring Boot 优雅关闭加入一些自定义机制

    个人创作公约:本人声明创作的所有文章皆为自己原创,如果有参考任何文章的地方,会标注出来,如果有疏漏,欢迎大家批判.如果大家发现网上有抄袭本文章的,欢迎举报,并且积极向这个 github 仓库 提交 i ...

  5. Spring-IOC 在非 web 环境下优雅关闭容器

    当我们设计一个程序时,依赖了Spring容器,然而并不需要spring的web环境时(Spring web环境已经提供了优雅关闭),即程序启动只需要启动Spring ApplicationContex ...

  6. Asp.net控制Tomcat启动关闭的实现方法

    一.场景 近日有个项目客户要求能自己配置相关权限.由于历史原因这个项目采用的是公司以前的权限系统.这个权限系统很强大,不过有个弊端,就是每增加一个权限菜单都要重启才能生效,不然就要等1天它缓存过期后才 ...

  7. Socket编程中的强制关闭与优雅关闭及相关socket选项

    以下描述主要是针对windows平台下的TCP socket而言. 首先需要区分一下关闭socket和关闭TCP连接的区别,关闭TCP连接是指TCP协议层的东西,就是两个TCP端之间交换了一些协议包( ...

  8. 使用RunTime.getRunTime().addShutdownHook优雅关闭线程池

    有时候我们用到的程序不一定总是在JVM里面驻守,可能调用完就不用了,释放资源. RunTime.getRunTime().addShutdownHook的作用就是在JVM销毁前执行的一个线程.当然这个 ...

  9. 优雅关闭web服务的方式

    优雅关闭web服务 DBHelper, err = gorm.Open("mysql", "root:root@(115.159.59.129:3306)/test?ch ...

  10. Tomcat是否关闭 maxEntriesLocalHeap

    EHCache does not allow attribute "maxEntriesLocalHeap". 这个错误是由于这个属性不支持2.5以下版本 故更新ehcache版本 ...

随机推荐

  1. CodeForces 1141F2 贪心 离散化

    CodeForces 1141F2 贪心 离散化 题意 给定一个序列,要求我们找出最多数量的不相交区间,每个区间和都相等. 思路 一开始没有头绪,不过看到 \(n \le 1500\) 后想到可以把所 ...

  2. 昇腾实践丨ATC模型转换动态shape问题案例

    本文分享自华为云社区<ATC模型转换动态shape问题案例>,作者:昇腾CANN. ATC(Ascend Tensor Compiler)是异构计算架构CANN体系下的模型转换工具:它可以 ...

  3. Materialize MySQL引擎:MySQL到Click House的高速公路

    摘要: MySQL到ClickHouse数据同步原理及实践 引言 熟悉MySQL的朋友应该都知道,MySQL集群主从间数据同步机制十分完善.令人惊喜的是,ClickHouse作为近年来炙手可热的大数据 ...

  4. DarkMode(2):深色模式解决方案——css颜色变量实现Dark Mode

    暗黑模式实现,最初的设计,就是参考之前的主题模式.所谓多套主题/配色/皮肤,就是我们很常见的换肤功能.换肤简单的实现就是更换 css实现不同样式呈现不同肤色. 之前做不同颜色的皮肤,暗黑模式可以单做其 ...

  5. 从此告别写 SQL!DataLeap 帮你零门槛完成“数据探查”

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 在日常数据处理工作中,产品.运营.研发或数据分析师经常会面临数据量大且混乱.质量参差不齐的问题,需要花费大量时间和 ...

  6. IDC《中国边缘云市场解读 (2022)》:阿里云蝉联中国公有云市场第一

    国际权威咨询公司IDC发布<中国边缘云市场解读(2022 )>报告,中国边缘公有云服务市场,阿里云蝉联第一. 市场蝉联第一,"边缘"生长强劲 近期,全球领先的IT市场研 ...

  7. The 18th Zhejiang Provincial Collegiate Programming Contest 补题记录(ACFGJLM)

    补题链接:Here A. League of Legends 签到题,求和判断即可 ll suma, sumb; void solve() { ll x; for (int i = 1; i < ...

  8. springboot线程池的使用方式2

    一.简单介绍 方式1:Executors.newCachedThreadPool线程池.Executors有7种不同的线程池. private static final ExecutorService ...

  9. 活动回顾|阿里云 Serverless 技术实践营 Serverless +AI 专场

    8月25日"阿里云Serverless技术实践营( Serverless + AI 专场)"北京站圆满落幕.活动受众以关注 Serverless +AI 技术的开发者.企业决策人. ...

  10. 7、SpringBoot-mybatis-plus引入

    系列导航 springBoot项目打jar包 1.springboot工程新建(单模块) 2.springboot创建多模块工程 3.springboot连接数据库 4.SpringBoot连接数据库 ...