转载自: 问题解决记录之linux系统tomcat部署项目通过shutdown无法正常关闭进程,多次启动会有多个进程

经过百度得知,是由于tomcat中线程为正常关闭。需要通过杀掉进程的方式来强行来停掉tomcat。

解决方法如下:

第一步 :vim修改tomcat下bin/catalina.sh文件,添加点如下标红的代码,主要是记录tomcat的pid,如下:

PRGDIR=`dirname "$PRG"`


**if [ -z "$CATALINA\_PID" ]; then
 CATALINA\_PID=$PRGDIR/CATALINA\_PID
fi** 

如图

 

第二步 vim tomcat的shutdown.sh文件,在最后一行加上-force:

如下图:

 

 

参考文章链接(写的相当详细):https://www.cnblogs.com/nuccch/p/9052692.html

以下是直接复制的

问题描述

通常,我们都会直接使用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:释放资源
 }
}

 另外还有一个参考链接(能够找到未关闭的进程):https://blog.csdn.net/bobozai86/article/details/93722270

执行了tomcat的shutdown脚本后,java进程仍然存在,认为tomcat的关闭脚本不可靠。好吧,其实shutdown.sh无法停止,不是tomcat问题,是应用有问题,否则可以跑一个空tomcat,保证shutdown百分之百生效。

先说一下tomcat的大概关闭过程:
1 停止连接处理线程Accepter,停止接受新的请求
2 关闭tomcat自身的资源,例如各种service,连接器,protocol,container
3 然后tomcat主线程结束了,就是执行BootStrap这个类的线程

这是一个平滑关闭的过程,但是什么时候会导致所谓”tomcat无法关闭”?要更正一点,不是tomcat无法关闭,是执行tomcat的这个jvm无法关闭,这是本质的不同。

最根本的原因是:当前运行tomcat的jvm里还有非deamon的线程没有结束执行,例如被阻塞,或者还在执行任务。这个现象就是tomcat 端口都已经关闭了,但是java进程还在。tomcat的停止只是结束了自己的线程,关闭了自己的资源。但是应用开启的非deamon线程,这个 tomcat是无能为力的。

netstat -ano|grep 8083 /*可以通过此确认tomcat是否已关闭*/
那么JVM什么时候停止?没有非deamon的线程在运行就停止了,或者说应用自身的非deamon线程处理完所有事情结束了自己,jvm就停止了。

那么当发现tomcat停止了,但是ps -ef|grep tomcat进程依然在,如何查看应用那个地方线程是非deamon的呢?

查看办法很简单,执行如下命令:

$JAVA_HOME/bin/jstack <pid>

例:

[root@localhost bin]# ps -ef|grep tomcat
root 1513 1 2 23:41 pts/1 00:00:01 /usr/local/share/java/jdk1.6.0_25/bin/java -Djava.util.logging.config.file=/opt/apache-tomcat-6.0.32/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.endorsed.dirs=/opt/apache-tomcat-6.0.32/endorsed -classpath /opt/apache-tomcat-6.0.32/bin/bootstrap.jar -Dcatalina.base=/opt/apache-tomcat-6.0.32 -Dcatalina.home=/opt/apache-tomcat-6.0.32 -Djava.io.tmpdir=/opt/apache-tomcat-6.0.32/temp org.apache.catalina.startup.Bootstrap start
root 1544 1462 0 23:42 pts/1 00:00:00 grep --color=auto tomcat

可以看到tomcat进展pid为1513,则下面定位到jdk/bin目录,执行jstack1513 查看工程里到底哪里开启的新的非守护线程?

$ cd /home/software/jdk1.7.0_79/bin
$ jstack 1513

调用jstack查看:

[root@localhost bin]# jstack 1513
2011-07-12 23:44:00
Full thread dump Java HotSpot(TM) Client VM (20.0-b11 mixed mode, sharing):


**"Attach Listener" daemon prio=10 tid=0xb41d7c00 nid=0x606 waiting on condition [0x00000000]
java.lang.Thread.State: RUNNABLE**"TP-Monitor" daemon prio=10 tid=0xb41d6400 nid=0x5fa in Object.wait() [0xb3e0b000]
java.lang.Thread.State: TIMED\_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x87143720> (a org.apache.tomcat.util.threads.ThreadPool$MonitorRunnable)
at org.apache.tomcat.util.threads.ThreadPool$MonitorRunnable.run(ThreadPool.java:565)
- locked <0x87143720> (a org.apache.tomcat.util.threads.ThreadPool$MonitorRunnable)
at java.lang.Thread.run(Thread.java:662)


**"TP-Processor4" daemon prio=10 tid=0xb41d4c00 nid=0x5f9 runnable [0xb3e5c000****]
java.lang.Thread.State: RUNNABLE**
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:408)
- locked <0x87124770> (a java.net.SocksSocketImpl)
at java.net.ServerSocket.implAccept(ServerSocket.java:462)
at java.net.ServerSocket.accept(ServerSocket.java:430)
at org.apache.jk.common.ChannelSocket.accept(ChannelSocket.java:311)
at org.apache.jk.common.ChannelSocket.acceptConnections(ChannelSocket.java:668)
at org.apache.jk.common.ChannelSocket$SocketAcceptor.runIt(ChannelSocket.java:879)
at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:690)
at java.lang.Thread.run(Thread.java:662)

其中看到

"Attach Listener" daemon prio=10 tid=0xb41d7c00 nid=0x606 waiting on condition [0x00000000]

 java.lang.Thread.State: RUNNABLE

这行,最前边的"Attach Listener" 是线程名, 紧跟其后的 daemon是线程的守护状态,

其中主线程不是daemon的,所以是这样:

"main" prio=10 tid=0xb6d05000 nid=0x5ea runnable [0xb6ee9000]
java.lang.Thread.State: RUNNABLE
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:408)
- locked <0x871644a8> (a java.net.SocksSocketImpl)
at java.net.ServerSocket.implAccept(ServerSocket.java:462)
at java.net.ServerSocket.accept(ServerSocket.java:430)
at org.apache.catalina.core.StandardServer.await(StandardServer.java:431)
at org.apache.catalina.startup.Catalina.await(Catalina.java:676)
at org.apache.catalina.startup.Catalina.start(Catalina.java:628)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:289)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:414)