zoukankan      html  css  js  c++  java
  • 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无法正常关闭

  • 相关阅读:
    SQL
    HTTP协议
    工具命令
    安全策略
    日志与审核
    python视频教程免费下载,百度云网盘资源,全套!
    《Python基础教程(第3版)》PDF电子版百度云网盘免费下载
    老男孩Python全栈开发视频教程全套完整版!免费分享!
    Pycharm激活码分享,2020最新Pycharm永久激活码~
    老男孩Python视频教程全套完整版!无偿分享~
  • 原文地址:https://www.cnblogs.com/nuccch/p/9052692.html
Copyright © 2011-2022 走看看