tomcat無法正常關閉問題分析及解決
問題描述
通常,我們都會直接使用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無法正常關閉問題分析及解決