深入剖析Tomcat(十五、十六) 关闭钩子,保证Tomcat的正常关闭

《深入剖析Tomcat》书中第十五章讲解了如何通过配置XML的方式来配置Tomcat的各个组件,并通过Digester库来解析XML。我们常操作的xml文件应该就是 server.xml这个文件,当在一台机器上部署多个Tomcat时,就必须修改连接器和 [“关闭Tomcat”程序] 监听的端口号,以解决端口冲突,即8005和8080 两个端口号。

<Server port="8005" shutdown="SHUTDOWN" debug="0"><!-- Uncomment these entries to enable JMX MBeans support --><Listener className="org.apache.catalina.mbeans.ServerLifecycleListener"debug="0"/><Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"debug="0"/><!-- Global JNDI resources --><GlobalNamingResources><!-- Test entry for demonstration purposes --><Environment name="simpleValue" type="java.lang.Integer" value="30"/><!-- Editable user database that can also be used byUserDatabaseRealm to authenticate users --><Resource name="UserDatabase" auth="Container"type="org.apache.catalina.UserDatabase"description="User database that can be updated and saved"></Resource><ResourceParams name="UserDatabase"><parameter><name>factory</name><value>org.apache.catalina.users.MemoryUserDatabaseFactory</value></parameter><parameter><name>pathname</name><value>conf/tomcat-users.xml</value></parameter></ResourceParams></GlobalNamingResources><Service name="Tomcat-Standalone"><Connector className="org.apache.catalina.connector.http.HttpConnector"port="8080" minProcessors="5" maxProcessors="75"enableLookups="true" redirectPort="8443"acceptCount="10" debug="0"/><Engine name="Standalone" defaultHost="localhost" debug="0"><Logger className="org.apache.catalina.logger.FileLogger"prefix="catalina_log." suffix=".txt"timestamp="true"/><Realm className="org.apache.catalina.realm.UserDatabaseRealm"debug="0" resourceName="UserDatabase"/><Host name="localhost" debug="0" appBase="webapps"unpackWARs="true" autoDeploy="true"><Logger className="org.apache.catalina.logger.FileLogger"directory="logs" prefix="localhost_log." suffix=".txt"timestamp="true"/></Host></Engine></Service>
</Server>

第十五章的内容就不展开讲了,我们直接来到第十六章:“关闭钩子”。

上一章中,讲到了Tomcat可以通过接收一条“关闭消息”的方式来正常关闭Tomcat(执行StandardServer的stop方法),代码逻辑如下图所示

server.await() 方法监听8005端口

但是正常情况下我们想停止Tomcat时,很少会向Tomcat的8005端口发送一条关闭消息(甚至很多同学都不知道还有这个机制),而是直接kill掉Tomcat对应的java进程,如果直接kill 进程的话,上图中的代码就根本不会走到第3步,“关闭方式1”这条消息也不会打印出来(已亲测过)。

那Tomcat是如何保证关闭流程能够在kill 进程时也能触发的呢?

这就得益于Java的设计了,Java 为程序员提供了一种优雅的方法,可以在关闭JVM进程时执行一些代码,这样就能确保那些负责善后处理的代码肯定能够执行。

在Java中,虚拟机会对两类事件进行响应,然后执行关闭操作:

  • 当调用System.exit() 方法或程序的最后一个非守护进程线程退出时,应用程序正常退出。
  • 用户突然强制虚拟机中断运行,例如用户按CTRL+C 快捷键或kill掉这个java进程。

虚拟机在执行关闭操作时,会经过以下两个阶段:

  1. 虚拟机启动所有已经注册的关闭钩子(如果有的话)。关闭钩子是先前已经通过Runtime类注册的线程,所有的关闭钩子会并发执行,直到完成任务;
  2. 虚拟机根据情况调用所有没有被调用过的终结器 (finalizer)。

本章重点说明第一个阶段,因为该阶段允许程序员告诉虚拟机在应用程序关闭时需要执行哪些清理代码。StandardServer#Stop() 方法就可以安排在这个阶段。

关闭钩子

上面提到的关闭钩子是啥呢?其实很简单,任何一个继承了Thread类的线程类都可以当做一个关闭钩子,钩子怎么生效呢?使用下面这行代码就能生效

Runtime.getRuntime().addShutdownHook(关闭钩子的对象);

下面我们设计一个关闭钩子,来优雅的关闭Tomcat。

先定义钩子类

该类是Bootstrap启动类的一个内部类,继承了Thread类,拥有一个Server组件实例的引用,以便在run方法中调用Server组件的stop()方法。

然后是将这个关闭钩子注册到当前JVM进程中

我这里将它安排在 server组件的 start() 方法后(即Tomcat启动后)注册进来。如下图所示

然后通过Bootstrap启动Tomcat类后,kill 进程,可以看到关闭钩子类的run方法被执行了,其实就是,关闭钩子对应的线程被创建了,然后执行了该线程的run方法。而“关闭方式1”这条日志和添加钩子前一样,不会打印。

测试结束,可以看到咱们自定义的关闭钩子MyShutDownHook成功完成使命:在被杀进程时执行了Server组件的stop() 方法。

注意!关闭钩子是属于Java的,并不单属于Tomcat,所有基于java的程序都可以定义关闭钩子,大家千万不要被局限了。

kill 与 kill -9

kill 的杀,比较有同情心,杀之前问问该进程有没有遗言,有遗言的话就说,于是进程开始巴拉巴拉交代遗言(执行所有关闭钩子),交代完之后,系统刽子手就手起刀落,杀死该进程。特殊情况下,该进程是个话痨,遗言巴拉了半天也不完,于是系统刽子手就不管它了,于是该进程就逃过一截 (キ`゚Д゚´)!!  ,这也就是我们常见的 kill 一个进程却 kill 不掉的情况。

kill -9 的杀,是个无情的刽子手,用户一发出 kill -9的执行,系统刽子手二话不说咔嚓一下就将该进程杀死了,管你有什么遗言没有,就是不给你说的机会。所以说执行 kill -9 命令要慎重,避免该进程本来有遗言要说却没说出来,造成对应故障。

Tomcat是怎么使用关闭钩子的

Tomcat的启动入口其实是org.apache.catalina.startup.Catalina类,该类中有一个自定义的关闭钩子,如下所示,功能和我们上面定义的一样,都是去调用server组件的stop方法。

然后在Catalina的start()方法中注册了这个钩子,在注册钩子前其实已经执行了server组件的start()方法,这个我没截出来,你知道即可。

在stop() 方法中做了移除关闭钩子的操作

以上就是Tomcat使用关闭钩子的方式。

为什么在stop() 方法中要移除关闭钩子呢?

这里通过几个实验来说明问题

前提布置

基于我们开篇的自定义钩子的代码,在两条日志中均加上线程名,1.正常关闭Tomcat的日志  2.通过关闭钩子关闭Tomcat的日志

StandardServer的stop()方法加一段线程阻塞的代码,模拟长时间的代码执行。在入口处加上日志

然后进行实验

实验一:仅通过kill 命令关闭Tomcat

关闭钩子触发,stop()方法正常运行,一切正常。

实验二:仅通过发送“shutdown”消息关闭Tomcat

还是通过Stopper类来发送shutdown消息

public class Stopper {public static void main(String[] args) {// the following code is taken from the Stop method of// the org.apache.catalina.startup.Catalina classint port = 8005;try {Socket socket = new Socket("127.0.0.1", port);OutputStream stream = socket.getOutputStream();String shutdown = "SHUTDOWN";for (int i = 0; i < shutdown.length(); i++)stream.write(shutdown.charAt(i));stream.flush();stream.close();socket.close();System.out.println("The server was successfully shut down.");} catch (IOException e) {System.out.println("Error. The server has not been started.");}}
}

结果:正常关闭流程走完,JVM进程即将终止时又触发了关闭钩子,导致 StandardServer#stop() 方法重复执行并报错了。

实验三:发送“shutdown”消息后马上执行 kill 命令

StandardServer#stop() 仍然被执行了两次,导致报错。

实验四:仅发送“shutdown”消息,但是stop方法中加上移除关闭钩子的方法

结果:Tomcat正常关闭,没有报错。可见stop() 方法中移除了关闭钩子后,在终止JVM进程时就不会再触发关闭钩子了。

实验五:stop方法中加上移除关闭钩子的方法,发送“shutdown”消息后马上执行 kill 命令

我来说下现象:发送“shutdown”消息后,stop() 方法开始执行,并移除了关闭钩子。接下来执行  kill 命令,可以看到进程被立刻终止了,并没有等待stop()方法执行完。分析一下执行 kill 命令后的现象,应该是系统发现该进程已经没有关闭钩子了(被刚才的stop方法移除掉了),于是认为这个进程不需要遗言,于是干脆利落的杀死了。

对比实验四的日志也可以发现在结尾少了两条 stop() 方法中的日志。

通过上述五个实验可以得出,在 stop() 方法中移除关闭钩子,可以防止用户守规矩的通过发送“shutdown”消息来关闭Tomcat时,关闭钩子又被触发,stop() 方法被重复执行的问题。但是“shutdown”消息和 kill 命令两种关闭方式最好别一起用,否则很容易达到 kill -9 的效果。

OK,本章的内容就到这里,本章主要介绍了Java中的“关闭钩子”机制,Tomcat运用了这个机制,最大程度的保证了用户在关闭Tomcat时,StandardServer#stop() 方法能够被执行,实现了Tomcat的优雅关闭。下一章我们来研究下Tomcat的启动脚本,敬请期待!

源码分享

https://gitee.com/huo-ming-lu/HowTomcatWorks

本章中自定义关闭钩子的代码在Bootstrap类中

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/39999.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring源码九:BeanFactoryPostProcessor

上一篇Spring源码八&#xff1a;容器扩展一&#xff0c;我们看到ApplicationContext容器通过refresh方法中的prepareBeanFactory方法对BeanFactory扩展的一些功能点&#xff0c;包括对SPEL语句的支持、添加属性编辑器的注册器扩展解决Bean属性只能定义基础变量的问题、以及一些…

Netty 粘包/拆包、解码工具类

1. 概述 1.1 粘包 发送 abc def&#xff0c;接收 abcdef 原因 滑动窗口&#xff1a;假设发送方 256 bytes 表示一个完整报文&#xff0c;但由于接收方处理不及时且窗口大小足够大&#xff0c;这 256 bytes 字节就会缓冲在接收方的滑动窗口中&#xff0c;当滑动窗口中缓冲了…

模拟 ADC 的前端

ADC 的 SPICE 模拟 反复试验的方法将信号发送到 ADC 非常耗时&#xff0c;而且可能有效也可能无效。如果转换器捕获电压信息的关键时刻模拟输入引脚不稳定&#xff0c;则无法获得正确的输出数据。SPICE 模型允许您执行的步是验证所有模拟输入是否稳定&#xff0c;以便没有错误…

尝试修改苍穹外卖为”李小罗餐厅“

学习苍穹外卖后&#xff0c;将其修改为自己所需要的项目&#xff0c;也是对苍穹外卖项目的加深理解 对项目之间的连接等关系进一步清晰&#xff0c;那么便开始吧 d1_开始修改 修改名字为”李小罗餐厅“ src\views\login\index.vue src\router.ts 结果展示 修改进来之后的展示…

上海站圆满结束!MongoDB Developer Day深圳站,周六见!

在过去两个周六的北京和上海 我们见证了两站热情高涨的 MongoDB Developer Day&#xff01; 近200位参会开发者相聚专业盛会 经过全天的动手实操和主题研讨会 MongoDB技能已是Next Level&#xff01; 最后一站Developer Day即将启程 期待本周六与各位在深圳相见&#xff0…

【Docker安装】OpenEuler系统下部署Docker环境

【Docker安装】OpenEuler系统下部署Docker环境 前言一、本次实践介绍1.1 本次实践规划1.2 本次实践简介二、检查本地环境2.1 检查操作系统版本2.2 检查内核版本2.3 检查yum仓库三、卸载Docker四、部署Docker环境4.1 配置yum仓库4.2 检查可用yum仓库4.3 安装Docker4.4 检查Docke…

Python题解Leetcode Hot100之矩阵

1. 矩阵置零 题目描述 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 解题思路 题目要求进行原地更改&#xff0c;也就是不能使用额外的空间&#xff0c;因此我们可以使用第一行的元素来记录对应的…

【LeetCode】十二、递归:斐波那契 + 反转链表

文章目录 1、递归2、leetcode509&#xff1a;斐波那契数列3、leetcode206&#xff1a;反转链表4、leetcode344&#xff1a;反转字符串 1、递归 函数自己调用自己 递归的4个点&#xff1a; 递归的例子&#xff1a;给一个数n&#xff0c;在斐波那契数列中&#xff0c;找到n对应的…

科研与英文学术论文写作指南——于静老师课程

看到了一个特别棒的科研与英文学术论文写作指南&#xff0c;理论框架实例。主讲人是中科院信息工程研究所的于静老师。推荐理由&#xff1a;写论文和读论文或者讲论文是完全不一样的&#xff0c;即使现在还没有发过论文&#xff0c;但是通过于老师的课程&#xff0c;会给后续再…

LSTM水质预测模型实践

0 引言 随着水质自动站的普及&#xff0c;监测频次越来越高&#xff0c;自动监测越来越准确。 水质站点增多&#xff0c;连续的水质监测数据&#xff0c;给水质预测提供更多的训练基础。 长短时记忆网络(LSTM)适用于多变量、连续、自相关的数据预测。 人工神经网络模型特点为的…

使用requests爬取拉勾网python职位数据

爬虫目的 本文是想通过爬取拉勾网Python相关岗位数据&#xff0c;简单梳理Requests和xpath的使用方法。 代码部分并没有做封装&#xff0c;数据请求也比较简单&#xff0c;所以该项目只是为了熟悉requests爬虫的基本原理&#xff0c;无法用于稳定的爬虫项目。 爬虫工具 这次…

LVS 负载均衡群集

一&#xff1a;LVS群集应用基础 1.1&#xff1a;概述 1.群集的类型 无论是哪种群集&#xff0c; 都至少包括两台节点服务器&#xff0c; 而对外表现为一个整体&#xff0c; 只提供一个访问入口。根据群集所针对的目标差异&#xff0c; 可分为以下三种类型。 负载均衡群集&a…

使用U盘重装系统

目录 一、 制作启动盘 1. 准备一个U盘和一台电脑 2. 下载win10安装包 二、安装操作系统 1. 插入系统安装盘 2. 通过进入BIOS界面进入到我们自己制作的启动盘上 三、安装成功后进行常规设置 一、 制作启动盘 1. 准备一个U盘和一台电脑 注意&#xff1a;提前备份好U盘内的…

JDK1.8下载、安装与配置完整图文2024最新教程

一、报错 运行Pycharm时&#xff0c;报错No JVM installation found. Please install a JDK.If you already have a JDK installed, define a JAVA_HOME variable in Computer >System Properties > System Settings > Environment Variables. 首先可以检查是否已安装…

【C语言】qsort()函数详解:能给万物排序的神奇函数

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:C语言 ⚙️操作环境:Visual Studio 2022 目录 一.qsort()函数的基本信息及功能 二.常见的排序算法及冒泡排序 三.逐一解读qsort()函数的参数及其原理 1.void* base 2.size_t num 3.size_t size 4.int (*compar)(c…

节水增效,蜂窝物联智能灌溉助力农业升级!

智能灌溉的优势主要体现在以下几个方面&#xff1a; 1. 提高效率&#xff1a;智能灌溉可以根据作物生长的不同阶段和环境条件自动调整灌溉时间和水量&#xff0c;减少人工干预的频率和时间&#xff0c;提高了灌溉效率。 2. 节约水资源&#xff1a;智能灌溉可以根据土壤湿度和…

Python爬虫实战案例——王者荣耀皮肤抓取

大家好&#xff0c;我是你们的老朋友——南枫&#xff0c;今天我们一起来学习一下该如何抓取大家经常玩的游戏——王者荣耀里面的所有英雄的皮肤。 老规矩&#xff0c;直接上代码&#xff1a; 导入我们需要使用到的&#xff0c;也是唯一用到的库&#xff1a; 我们要抓取皮肤其…

大陆ARS548使用记录

一、Windows连接上位机 雷达是在深圳路达买的&#xff0c;商家给的资料中首先让配置网口&#xff0c;但我在使用过程中一直出现无法连接上位机的情况。接下来说说我的见解和理解。 1.1遇到的问题 按要求配置好端口后上位机无连接不到雷达&#xff0c;但wireshark可以正常抓到数…

PyPDF2拆分PDF文件的高级应用:指定拆分方式

本文目录 前言一、拆分方式选择1、代码讲解2、实现效果图3、完整代码前言 前两篇文章,分别讲解了将使用PyPDF2将PDF文档分割成为单个页面、在分割PDF文档时指定只分割出指定页面,如果你还没有看过,然后有需要的话,可以去看一下,我把文章链接贴到这里: PyPDF2拆分PDF文件…

Nuxt3 的生命周期和钩子函数(九)

title: Nuxt3 的生命周期和钩子函数&#xff08;九&#xff09; date: 2024/7/3 updated: 2024/7/3 author: cmdragon excerpt: 摘要&#xff1a;本文介绍了Nuxt3中与Vite相关的五个生命周期钩子&#xff0c;包括vite:extend、vite:extendConfig、vite:configResolved、vite…