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无法正常关闭问题分析及解决的更多相关文章
- 启动Tomcat一闪而过——分析及解决过程
		启动Tomcat一闪而过--分析及解决过程 嗯,昨天将有关JDK的知识稍微整理了一下,现在稍微整理一下有关Tomcat的! 1:Tomcat是什么? Tomcat是当今世界上使用最为广泛的.开源免费的 ... 
- tomcat 内存溢出原因分析及解决
		一.错误提示:java.lang.OutOfMemoryError: Java heap space [原因分析] tomcat默认可以使用内存为128MB,在较大型的应用项目中不足以满足运行要求,在 ... 
- nginx和Tomcat集成后发生的重定向问题分析和解决
		nginx和Tomcat集成后发生的重定向问题分析和解决 Tomcat前端配置一个HTTP服务器应该是大部分应用的标配了,基本思路就是所有动态请求都反向代理给后端的Tomcat,HTTP服务器来处 理 ... 
- Tomcat系统部署启动问题分析一例[sudo 启动]
		今天的系统获取新的版本后部署时突然tomcat无法启动,而比较版本的变化内容,也就是几个jsp和js文件的变化,对于web.xml等都没有调整. 这个问题很是奇怪,下面把步骤总结一下,以避免类似的问题 ... 
- TCP粘包问题分析和解决(全)
		TCP通信粘包问题分析和解决(全) 在socket网络程序中,TCP和UDP分别是面向连接和非面向连接的.因此TCP的socket编程,收发两端(客户端和服务器端)都要有成对的socket,因此,发送 ... 
- oracle执行update语句时卡住问题分析及解决办法
		转载:http://www.jb51.net/article/125754.htm 这篇文章主要介绍了oracle执行update语句时卡住问题分析及解决办法,涉及记录锁等相关知识,具有一定参考价值, ... 
- 改进动态设置query cache导致额外锁开销的问题分析及解决方法-mysql 5.5 以上版本
		改进动态设置query cache导致额外锁开销的问题分析及解决方法 关键字:dynamic switch for query cache, lock overhead for query cach ... 
- (4.20)SQL Server数据库启动过程,以及启动不起来的各种问题的分析及解决技巧
		转自:指尖流淌 https://www.cnblogs.com/zhijianliutang/p/4085546.html SQL Server数据库启动过程,以及启动不起来的各种问题的分析及解决技巧 ... 
- Tomcat几种启动报错及解决办法
		今天真跪了,tomcat的错想到想不到的都遇到了.不记录一下都愧对今天愁掉的hair 在此之前分享一个集错网站,应该是程序员必备的网站之一,不过纯英文,小酸爽 Tags - Stack Overflo ... 
随机推荐
- ThinkPad E470笔记本电脑无声音、无线wifi功能(灰色)
			最近有同事找我看他的笔记本没有wifi,型号是ThinkPadE470 ,上网搜了下提问的挺多,写一个看看有什么帮助没 看了下笔记本wifi标志是灰色显示只有飞行模式,启用了一下热键 fn+F3 没什 ... 
- Python基础——4高阶函数
			高阶函数 函数本身可用变量指向,把变量当做函数参数的函数成为高阶函数 map and reduce map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每 ... 
- 第1章 初始Docker容器
			1.1 什么是Docker slogan:Build Ship Run Any App Anywher.关键在于Ship,通过把程序和程序运行所需要的环境一起交付. Linux容器技术: Docker ... 
- PHP依赖注入原理与用法分析
			https://www.jb51.net/article/146025.htm 本文实例讲述了PHP依赖注入原理与用法.分享给大家供大家参考,具体如下: 引言 依然是来自到喜啦的一道面试题,你知道什么 ... 
- keras 中模型的保存
			参考:https://www.cnblogs.com/weiyinfu/p/9788179.html#0 1.model.summary() 这个函数会打印模型结构,但是仅仅是打印到控制台,不能保存 ... 
- Filebeat原理与简单配置入门
			Filebeat工作原理 Filebeat由两个主要组件组成:prospectors 和 harvesters.这两个组件协同工作将文件变动发送到指定的输出中. Harvester(收割机):负责读取 ... 
- 排序学习实践---ranknet方法
			要: 1 背景 随着移动互联网的崛起,越来越多的用户开始习惯于从手机完成吃.喝.玩.乐.衣.食.住.行等各个方面的需求.打开手机,点开手淘.美团等APP,商品玲玲满目,而让用户将所有商品一页 ... 
- Flask 快速使用 进阶——  (2)
			案例:可配置发送信息的系统 假如我们有这样的一个系统,可以发送短息,邮件和微信,后期可能还会增加一些平台,怎么才可以做到快速切换的去使用某种功能呢,在这里我会通过在配置文件中简单的配置就可以实现 在项 ... 
- vue服务器端渲染
			Vue.js 是构建客户端应用程序的框架.默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM.然而,也可以将同一个组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏 ... 
- Python——高阶函数——map filter zip
			一.map函数 1.作用:它接收一个函数 f 和一个 list,并通过把函数 f 依次作用在 list 的每个元素上,得到一个新的 list 并返回. 2.实例 def f(x): return x* ... 
