Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >tomcat无法正常关闭问题分析及解决

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

作者头像
编程随笔
发布于 2019-09-11 08:15:17
发布于 2019-09-11 08:15:17
2.4K00
代码可运行
举报
文章被收录于专栏:后端开发随笔后端开发随笔
运行总次数:0
代码可运行

问题描述

通常,我们都会直接使用tomcat提供的脚本执行关闭操作,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 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进程依然存在:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 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脚本内容:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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方法的地方可以看到如下信息:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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类的实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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()方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 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()方法,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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()方法中执行相应的关闭操作。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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无法正常关闭

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-05-17 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【Tomcat源码分析】揭秘 Tomcat 启动-初篇
说到 Tomcat 的启动,我们常需运行“tomcat/bin/startup.sh”脚本,但脚本内容究竟为何?不妨一探究竟。
@派大星
2024/09/13
1030
【Tomcat源码分析】揭秘 Tomcat 启动-初篇
Tomcat源码解析(二): Bootstrap和Catalina
ps:对于非正常方式退出Java虚拟机,例如杀进程,系统断电等,这些情况下,shutdownHook不会被执行
冬天vs不冷
2025/01/21
890
Tomcat源码解析(二): Bootstrap和Catalina
【Tomcat】《How Tomcat Works》英文版GPT翻译(第十七章)
This chapter focuses on Tomcat startup using two classes in the org.apache.catalina.startup package, Catalina and Bootstrap. The Catalina class is used to start and stop a Server object as well as parse the Tomcat configuration file, server.xml. The Bootstrap class is the entry point that creates an instance of Catalina and calls its process method. In theory, these two classes could have been merged. However, to support more than one mode of running Tomcat, a number of bootstrap classes are provided. For example, the aforementioned Bootstrap class is used for running Tomcat as a stand-alone application. Another class, org.apache.catalina.startup.BootstrapService, is used to run Tomcat as a Windows NT service.
阿东
2024/01/29
3340
【Tomcat】《How Tomcat Works》英文版GPT翻译(第十七章)
tomcat-整启动流程-源码解析
上文了解了大致tomat的相关架构,那么本文是针对tomcat的启动流程进行了解,。tomcat是通过Bootstrap的main方法进行启动,然后通过catalina对象中创建server.xml的解析器,一步到位创建出大部分组件,通过责任链模式进行层层管理。细节可以跟着调试代码关注如下。
逍遥壮士
2022/12/01
7320
tomcat-整启动流程-源码解析
tomcat-超详细的启动流程(init)
本质上tomcat的启动流程和总体架构都离不开server.xml。在Server.xml中我们可以看到一些我们比较熟悉的配置。
虞大大
2020/08/26
2.8K0
tomcat-超详细的启动流程(init)
Linux下重启tomcat命令
Linux下如何重启tomcat?tomcat作为目前主流的web服务器,做一些修改之后需要重启才能生效,下面为大家分享一下Linux下重启tomcat具体方法。
用户1685462
2021/07/28
15.7K0
16.3 安装Tomcat
安装Tomcat目录概要 cd /usr/local/src wget https://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-8/v8.5.24/bin/apache-tomcat-8.5.24.tar.gz tar zxvf apache-tomcat-8.5.20.tar.gz mv apache-tomcat-8.5.20 /usr/local/tomcat /usr/local/tomcat/bin/startup.sh ps aux|g
运维小白
2018/02/06
7430
16.3 安装Tomcat
管理-Linux下Tomcat常用服务管理方式
5. 如果你想直接干掉Tomcat,你可以使用kill命令,直接杀死Tomcat进程
秋日芒草
2022/01/12
7630
Tomcat 日志切割
1 安装cronolog 1[root@app_51 ~]$ yum install -y cronolog 2[root@app_51 ~]$ which cronolog 3/usr/sbin/cronolog 2 修改tomcat的catalina.sh 1 [ghl@app_51 ~]$ cd /home/ghl/tomcat/apache-tomcat-8.5.37/bin/ 2 [ghl@app_51 bin]$ cp catalina.sh catalina.sh.bak 3 #需要将第
HaydenGuo
2019/12/13
1.1K0
Tomcat 日志切割
Tomcat卷一 ----架构和初始化源码分析
1) Tomcat 最初由Sun公司的软件架构师 James Duncan Davidson 开发,名称为 “JavaWebServer”。
大忽悠爱学习
2022/05/09
9500
Tomcat卷一 ----架构和初始化源码分析
探秘Tomcat——启动篇
tomcat作为一款web服务器本身很复杂,代码量也很大,但是模块化很强,最核心的模块还是连接器Connector和容器Container。具体请看下图: 从图中可以看出 a. 高亮的两块是Conn
JackieZheng
2018/01/16
2.2K0
探秘Tomcat——启动篇
部署云服务器--(3) Linux系统用脚本方式实现Tomcat的开机自启动
此篇教程在我的电脑(deepin-generic)上是可以用的,做好脚本后重启立即生效,但是在服务器(CentOS-7.3),需要开机后等待十几分钟,原因不明啊,很迷,另外记得服务器的防火墙开了8080端口(或者你设置的tomcat的端口).
浩Coding
2019/07/02
1.6K0
部署云服务器--(3) Linux系统用脚本方式实现Tomcat的开机自启动
Spring注解事务诡异提交全面解析
应用上线的时候,正常调用Tomcat的shutdown.sh脚本,事务执行一半异常提交。伪代码如下:
小程故事多
2018/08/22
8630
Spring注解事务诡异提交全面解析
centos下tomcat安装调试
1、在Linux系统下,重启Tomcat使用命令操作的! ** 方法一:** 首先,进入Tomcat下的bin目录 cd /usr/local/tomcat/bin 使用Tomcat关闭命令 .
Albert陈凯
2018/04/04
1.3K0
Tomcat启动过程源码解读
根据Tomcat源码来看一下Tomcat启动过程都做了什么 部分代码为主要流程代码,删去了try-catch以及一些校验逻辑,方便理解主流程 先来一张启动过程时序图,了解一下启动顺序 Tomcat启动
欠扁的小篮子
2018/04/11
1.1K0
Tomcat启动过程源码解读
Tomcat_03_监控
查考:http://www.oracle.com/technetwork/java/javase/downloads/index.html
Cyylog
2020/08/19
5470
tomcat详解
1.jre-jdk-jvm介绍 jre java runtime enviroment java运行环境 jdk java delelopment kit java开发环境(很多内容) = jre + 额外java工具 jvm java virtual machine java虚拟机 **1份代码 想在不同的系统使用 **java 程序代码 运行在java虚拟机中 只要系统能有java环境(java虚拟机) 就可以运行代码 **1份代码 处处使用问题 代码可移植性 **对于 java虚拟机 一般关注 内存
派大星在吗
2021/12/17
1.4K0
Tomcat 优雅关闭之路
本文通过阅读Tomcat启动和关闭流程的源码,深入分析不同的Tomcat关闭方式背后的原理,让开发人员能够了解在使用不同的关闭方式时需要注意的点,避免因JVM进程异常退出导致的各种非预见性错误。
2020labs小助手
2020/02/13
3.7K0
as5.4安装jdk及tomcat
Base OS:Redhat5.4. jdk version:jdk-6u22-linux-i586-rpm.bin tomcat version: apache-tomcat-5.5.31 download jdk form official website:  http://java.sun.com/    jdk-6u22-linux-i586-rpm.bin download tomcat from official website:  http://tomcat.apache.org/ ap
cloudskyme
2018/03/20
7700
Linux系统之tomcat的安装方法
江湖有缘
2023/07/27
5130
Linux系统之tomcat的安装方法
相关推荐
【Tomcat源码分析】揭秘 Tomcat 启动-初篇
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验