java 关闭 tomcat_tomcat无法正常关闭问题分析及解决

801f353fa3fcb1a5a4f332166d1c1672.png

问题描述

通常,我们都会直接使用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 。

那么,为什么使用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之后进程依然存在

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

4e960cf7ccec530aac272270392fe2af.png

解决方案

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

public class ResListener implements ServletContextListener {

public void contextInitialized(ServletContextEvent sce) {

//TODO:初始化资源

}

// 释放资源,否则容器无法正常关闭

public void contextDestroyed(ServletContextEvent sce) {

//TODO:释放资源

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值