问题描述

通常,我们都会直接使用tomcat提供的脚本执行关闭操作,如下:

# sh bin/shutdown.sh
Using CATALINA_BASE: /usr/local/apache-tomcat-7.0.59
Using CATALINA_HOME: /usr/local/apache-tomcat-7.0.59
Using CATALINA_TMPDIR: /usr/local/apache-tomcat-7.0.59/temp
Using JRE_HOME: /usr/local/jdk1.8.0_121
Using CLASSPATH: /usr/local/apache-tomcat-7.0.59/bin/bootstrap.jar:/usr/local/apache-tomcat-7.0.59/bin/tomcat-juli.jar

但是执行该关闭操作之后,有时候会发现tomcat进程依然存在:

# ps uax |grep tomcat
root 1199 0.0 0.0 9120 468 ? Ss 21:53 0:00 /sbin/dhclient -H centosx64_tomcat1 -1 -q -lf /var/lib/dhclient/dhclient-eth2.leases -pf /var/run/dhclient-eth2.pid eth2
root 2081 9.7 60.7 2192828 295224 pts/0 Sl 22:04 1:04 /usr/local/jdk1.8.0_121/bin/java -Djava.util.logging.config.file=/usr/local/apache-tomcat-7.0.59/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Dmoc.debug=true -Djava.endorsed.dirs=/usr/local/apache-tomcat-7.0.59/endorsed -classpath /usr/local/apache-tomcat-7.0.59/bin/bootstrap.jar:/usr/local/apache-tomcat-7.0.59/bin/tomcat-juli.jar -Dcatalina.base=/usr/local/apache-tomcat-7.0.59 -Dcatalina.home=/usr/local/apache-tomcat-7.0.59 -Djava.io.tmpdir=/usr/local/apache-tomcat-7.0.59/temp org.apache.catalina.startup.Bootstrap start
root 2192 0.0 0.1 103332 848 pts/0 S+ 22:15 0:00 grep tomcat

这时我们就只能通过强制杀死进程的方式停止Tomcat了:kill -9 <tomcat_process_id>

那么,为什么使用shutdown.sh无法正常停止Tomcat进程呢?

原因分析

停止Tomcat原理分析

我们先来看看tomcat实现关闭的原理是什么?如下为shutdown.sh脚本内容:

PRGDIR=`dirname "$PRG"`
EXECUTABLE=catalina.sh # Check that target executable exists
if $os400; then
# -x will Only work on the os400 if the files are:
# 1. owned by the user
# 2. owned by the PRIMARY group of the user
# this will not work if the user belongs in secondary groups
eval
else
if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then
echo "Cannot find $PRGDIR/$EXECUTABLE"
echo "The file is absent or does not have execute permission"
echo "This file is needed to run this program"
exit 1
fi
fi exec "$PRGDIR"/"$EXECUTABLE" stop "$@"

显然,shutdown.sh只是一个执行入口,真正执行关闭操作是在catalina.sh中实现的,继续查看catalina.sh脚本内容,在其中关于调用stop方法的地方可以看到如下信息:

eval "\"$_RUNJAVA\"" $LOGGING_MANAGER $JAVA_OPTS \
-Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
org.apache.catalina.startup.Bootstrap "$@" stop
# stop failed. Shutdown port disabled? Try a normal kill.
if [ $? != 0 ]; then
if [ ! -z "$CATALINA_PID" ]; then
echo "The stop command failed. Attempting to signal the process to stop through OS signal."
kill -15 `cat "$CATALINA_PID"` >/dev/null 2>&1
fi
fi

首先需要调用Tomcat的Bootstrap类,然后再通过kill命名停止Tomcat进程。但是注意 到在这里使用kill命令发送的信号为SIGTERM(15),也就是说有可能不能停止Tomcat进程(如:进程未释放系统资源)。

下面先追踪一下Bootstrap类的实现:

if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null==daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}

在Bootstrap的main方法中会根据在catalina.sh脚本传递的不同参数(start,stop)执行不同的方法。其中,当参数为stop时会调用stopServer()方法。

/**
* Stop the standalone server.
* @param arguments Command line arguments
* @throws Exception Fatal stop error
*/
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()方法,如下所示:

public void stopServer(String[] arguments) {

    if (arguments != null) {
arguments(arguments);
}
Server s = getServer();
if (s == null) {
// Create and execute our Digester
Digester digester = createStopDigester();
File file = configFile();
try (FileInputStream fis = new FileInputStream(file)) {
InputSource is =
new InputSource(file.toURI().toURL().toString());
is.setByteStream(fis);
digester.push(this);
digester.parse(is);
} catch (Exception e) {
log.error("Catalina.stop: ", e);
System.exit(1);
}
} else {
// Server object already present. Must be running as a service
try {
s.stop();
} catch (LifecycleException e) {
log.error("Catalina.stop: ", e);
}
return;
} // Stop the existing server
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);
}
}

如上所示,Tomcat进程的关闭操作需要做2件事:

第一:调用Bootstrap类的方法释放Tomcat进程所占用的资源。

第二:使用kill命令停止Tomcat进程:kill -15 <tomcat_process_id>

为什么停止Tomcat之后进程依然存在

Tomcat是一个Servlet容器,用于部署Serlvet程序(我们通常写的各种Java Web应用本质上就是一个Servlet程序)。也就说,在停止Tomcat时不仅仅需要释放Tomcat进程本身所占用的资源,还需要释放Serlvet程序所占用的资源。而出现“停止Tomcat之后进程依然存在”这种现象的主要原因就是:我们自己写的Java Web应用在Tomcat容器停止时没有正常释放所占用的系统资源,比如:线程池未关闭,输入输出流未关闭等等。我在实际开发中就曾遇到因Kafka客户端未关闭到导致Tomcat无法正常停止的情况。然而,这却是很多做Web应用开发的程序员未引起注意的地方。往往都是不能正常关闭就直接强制杀死进程,当然达到了目的,但这并不是一个很好的做法。

解决方案

我们必须确保在容器退出时正确地释放相应资源,如:实现ServletContextListener监听器接口,在contextDestroyed()方法中执行相应的关闭操作。

public class ResListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
//TODO:初始化资源
} // 释放资源,否则容器无法正常关闭
public void contextDestroyed(ServletContextEvent sce) {
//TODO:释放资源
}
}

【参考】

[1]. http://han.guokai.blog.163.com/blog/static/1367182712010731149286/ Tomcat无法正常关闭

tomcat无法正常关闭问题分析及解决的更多相关文章

  1. 启动Tomcat一闪而过——分析及解决过程

    启动Tomcat一闪而过--分析及解决过程 嗯,昨天将有关JDK的知识稍微整理了一下,现在稍微整理一下有关Tomcat的! 1:Tomcat是什么? Tomcat是当今世界上使用最为广泛的.开源免费的 ...

  2. tomcat 内存溢出原因分析及解决

    一.错误提示:java.lang.OutOfMemoryError: Java heap space [原因分析] tomcat默认可以使用内存为128MB,在较大型的应用项目中不足以满足运行要求,在 ...

  3. nginx和Tomcat集成后发生的重定向问题分析和解决

    nginx和Tomcat集成后发生的重定向问题分析和解决 Tomcat前端配置一个HTTP服务器应该是大部分应用的标配了,基本思路就是所有动态请求都反向代理给后端的Tomcat,HTTP服务器来处 理 ...

  4. Tomcat系统部署启动问题分析一例[sudo 启动]

    今天的系统获取新的版本后部署时突然tomcat无法启动,而比较版本的变化内容,也就是几个jsp和js文件的变化,对于web.xml等都没有调整. 这个问题很是奇怪,下面把步骤总结一下,以避免类似的问题 ...

  5. TCP粘包问题分析和解决(全)

    TCP通信粘包问题分析和解决(全) 在socket网络程序中,TCP和UDP分别是面向连接和非面向连接的.因此TCP的socket编程,收发两端(客户端和服务器端)都要有成对的socket,因此,发送 ...

  6. oracle执行update语句时卡住问题分析及解决办法

    转载:http://www.jb51.net/article/125754.htm 这篇文章主要介绍了oracle执行update语句时卡住问题分析及解决办法,涉及记录锁等相关知识,具有一定参考价值, ...

  7. 改进动态设置query cache导致额外锁开销的问题分析及解决方法-mysql 5.5 以上版本

    改进动态设置query cache导致额外锁开销的问题分析及解决方法 关键字:dynamic switch for query cache,  lock overhead for query cach ...

  8. (4.20)SQL Server数据库启动过程,以及启动不起来的各种问题的分析及解决技巧

    转自:指尖流淌 https://www.cnblogs.com/zhijianliutang/p/4085546.html SQL Server数据库启动过程,以及启动不起来的各种问题的分析及解决技巧 ...

  9. Tomcat几种启动报错及解决办法

    今天真跪了,tomcat的错想到想不到的都遇到了.不记录一下都愧对今天愁掉的hair 在此之前分享一个集错网站,应该是程序员必备的网站之一,不过纯英文,小酸爽 Tags - Stack Overflo ...

随机推荐

  1. sklearn使用——梯度下降及逻辑回归

    一:梯度下降: 梯度下降本质上是对极小值的无限逼近.先求得梯度,再取其反方向,以定步长在此方向上走一步,下次计算则从此点开始,一步步接近极小值.需要注意的是步长的取值,如果过小,则需要多次迭代,耗费大 ...

  2. 戏说春秋_i春秋 writeup

    <戏说春秋>第一关 图穷匕见 题目: 解:用winhex打开,拉到最后可发现一段编码 放到解密网站上解码. <戏说春秋>第二关 纸上谈兵 解:文中没有明确指出问题,也没有给出线 ...

  3. python 词云学习

    词云入门 三步曲 数据获取:使用爬虫在相关网站上获取文本内容 数据清洗:按一定格式对文本数据进行清洗和提取(文本分类,贴标签) 数据呈现:多维度呈现和解读数据(计算,做表,画图) 一 模块的安装 pi ...

  4. Eclipse安装Gradle插件

    1.window下安装Gradle见https://www.cnblogs.com/felixzh/p/9203271.html2.eclipse中依次打开help>Install new so ...

  5. volatile分析

    volatile三大特性: 1. 内存可见性 2.不保证原子性 3. 禁止重排序内存屏障的概念:memory barrier是一个CPU指令.指令逻辑:a.确保一些特定操作执行顺序 b.影响一些数据的 ...

  6. SkylineGlobe 7.0.1 & 7.0.2版本Web开发 如何实现对三维模型和地形的剖切展示

    现在很多三维项目中,不仅仅要用到三维地形,正射影像和矢量数据,还会融合到各种三维模型,包括传统的3DMax手工建模,BIM,倾斜摄影自动建模,激光点云模型,三维地质体模型等等. 三维平台首先要做的是把 ...

  7. 爬虫基础(三)-----selenium模块应用程序

    摆脱穷人思维 <三> :  培养"目标导向"的思维:  好项目永远比钱少,只要目标正确,钱总有办法解决. 一 selenium模块 什么是selenium?seleni ...

  8. 排序算法(sorting)

    学习到的排序算法的总结,包括对COMP20003中排序部分进行总结,部分图片来自COMP20003 有部分内容来自http://www.cnblogs.com/eniac12/p/5329396.ht ...

  9. js02-常用流程控制语句

    1.if语句 语法:if(条件){ 条件成立时执行 }else{ 条件不成立执行 } 例 var ji = 20; if(ji>=20){ console.log('恭喜你,吃鸡成功,大吉大利' ...

  10. JDBC工具类完整版!

    package com.aaa.util; import java.sql.*; import java.util.ArrayList; import java.util.HashMap; impor ...