Tomcat shutdown执行后无法退出进程问题排查及解决

5 篇文章 0 订阅

问题定位及排查

上周无意中调试程序在Linux上ps -ef|grep tomcat发现有许多tomcat的进程,当时因为没有影响系统运行就没当回事。而且我内心总觉得这可能是tomcat像nginx一样启动多个进程。

后来测试在一次升级后反馈说怎么现在tomcat进程无法shutdown?这让我有点意外,看来这个问题并没有这么简单。于是开始思考问题会出在哪里。

复现问题

先是另外一台服务器部署,然后shutdown后再ps进程是空的,这说明tomcat不会自动产生新的进程。那就有可能系统代码出了什么问题吧?最近另一个位同事有比较多的修改,可能是因为这些修改吧。光猜想也找不到问题,只好用jvisuale来看一下系统的dump,发现shutdown之后进程没有退出,而且里面有许多线程还在运行,有些还是线程池。

看来是有线程没有释放导致的泄露吧?于是用tail命令打开catalina.out查看最后shutdown.sh,在控制台输出了下面这些内容:

Nov 28, 2016 10:41:08 AM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [/] appears to have started a thread named [Component socket reader] but has failed to stop it. This is very likely to create a memory leak.

确实有许多的线程没有关闭,在关闭时还提示了泄漏。从这些线程的名字可以确认了,是这近新增了一个openfire的whack外部组件导致的。这个whack可以连接到openfire服务器,实现一套扩展组件服务的功能,我们主要用来发送IM消息。这样做的好处是开启线程数少,效率高,并发性能很不错。

查看代码

先看一下ExternalComponentManager的实现,因为它是用来外部扩展组件的管理者,我们的操作基本是根据它来完成的。

下面的代码便是是创建一个ExternalComponentManager,并且设置参数同时连接到服务器。

private void CreateMessageSender() {
    manager = new ExternalComponentManager(configHelper.getOpenfireHost(),
            configHelper.getOpenfireExternalCompPort());
    manager.setSecretKey(SENDER_NAME, configHelper.getOpenfirePwd());
    manager.setMultipleAllowed(SENDER_NAME, true);
    try {
        msc = new MessageSenderComponent("senderComponent", manager.getServerName());
        manager.addComponent(SENDER_NAME, msc);
    } catch (ComponentException e) {
        logger.error("CreateMessageSender error.", e);
    }
}

那么最重要的是在哪里启动了线程?毕竟最终影响系统的是线程没有关闭。所以沿着addComponent这调用看看吧:

public void addComponent(String subdomain, Component component, Integer port) throws ComponentException {
    if (componentsByDomain.containsKey(subdomain)) {
        if (componentsByDomain.get(subdomain).getComponent() == component) {
            // Do nothing since the component has already been registered
            return;
        }
        else {
            throw new IllegalArgumentException("Subdomain already in use by another component");
        }
    }
    // Create a wrapping ExternalComponent on the component
    ExternalComponent externalComponent = new ExternalComponent(component, this);
    try {
        // Register the new component
        componentsByDomain.put(subdomain, externalComponent);
        components.put(component, externalComponent);
        // Ask the ExternalComponent to connect with the remote server
        externalComponent.connect(host, port, subdomain);
        // Initialize the component
        JID componentJID = new JID(null, externalComponent.getDomain(), null);
        externalComponent.initialize(componentJID, this);
    }
    catch (ComponentException e) {
        // Unregister the new component
        componentsByDomain.remove(subdomain);
        components.remove(component);
        // Re-throw the exception
        throw e;
    }
    // Ask the external component to start processing incoming packets
    externalComponent.start();
}

代码也比较简单,就是创建了一个wapper类ExternalComponent将我们自己的Component包装了一下。其中最为重要的是最后一句:externalComponent.start();

public void start() {
    // Everything went fine so start reading packets from the server
    readerThread = new SocketReadThread(this, reader);
    readerThread.setDaemon(true);
    readerThread.start();
    // Notify the component that it will be notified of new received packets
    component.start();
}

原来这里启动了一个读取线程,用于接收Openfire服务器发来的数据流。查看线程构造函数:

public SocketReadThread(ExternalComponent component, XPPPacketReader reader) {
    super("Component socket reader");
    this.component = component;
    this.reader = reader;
}

可以看到,这个线程的名字是“Component socket reader”,在前面的日志里确实有这个线程。

解决问题

那么接下来的主要问题是如何关闭这个SocketReadThread,按理说会有相应的实现,发现externalComponent.start()这个方法有名字叫start,那么是不是有与其匹配的方法呢?确实有的一个shutdown的方法:

public void shutdown() {
    shutdown = true;
    // Notify the component to shutdown
    component.shutdown();
    disconnect();
}

原来这里调用了component.shutdown();最后还调用了一个disconnect,继续看代码:

private void disconnect() {
    if (readerThread != null) {
        readerThread.shutdown();
    }
    threadPool.shutdown();
    TaskEngine.getInstance().cancelScheduledTask(keepAliveTask);
    TaskEngine.getInstance().cancelScheduledTask(timeoutTask);
    if (socket != null && !socket.isClosed()) {
        try {
            synchronized (writer) {
                try {
                    writer.write("</stream:stream>");
                    xmlSerializer.flush();
                }
                catch (IOException e) {
                    // Do nothing
                }
            }
        }
        catch (Exception e) {
            // Do nothing
        }
        try {
            socket.close();
        }
        catch (Exception e) {
            manager.getLog().error(e);
        }
    }
}

发现这里就有了线程shutdown的调用,OK,说明就是它了。

因为最外层代码使用的是ExternalComponentManager,那么在ExternalComponentManager中调用了ExternalComponent shutdown的方法是removeComponent,那么就是它了。

也就是说只要在最后应用关闭时调用removeComponent方法就可以释放线程资源。这里当然就可以借助ServletContextListener来完成咯。

public class MessageSenderServletContextListener implements ServletContextListener{
    private final static Logger logger = LoggerFactory
            .getLogger(MessageSenderServletContextListener.class);

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        logger.debug("contextInitialized is run.");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        logger.debug("contextDestroyed is run.");
        MessageSender msgSender = SpringUtil.getBean(MessageSender.class);
        try {
            msgSender.shutdown();
            logger.debug("MessageSender is shutdown.");
        } catch (ComponentException e) {
            logger.error(e.getMessage());
        }
    }

}

实现contextDestroyed方法,从spring中获得MessageSender类,调用shutdown释放资源即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Tomcat是一个开源的Java Web服务器,它是Apache软件基金会的一个项目。当我们停止Tomcat服务器时,可能会遇到Tomcat shutdown进程仍在运行的情况。 出现这种情况通常是由于某些正在运行的任务或线程没有正确关闭所致。在关闭Tomcat服务器时,Tomcat会尝试结束所有正在运行的任务和线程,然后关闭自身。但有时候,如果存在某些无法正常结束的任务或线程,就会导致Tomcat shutdown进程仍然存在。 要解决这个问题,我们可以尝试以下几种方法: 1. 查找并终止相关的进程:使用任务管理器或命令行工具,查找并手动终止与Tomcat相关的进程。通常可以通过进程名称或进程ID来识别。终止这些进程后,Tomcat shutdown进程应该会消失。 2. 强制关闭进程:如果无法通过常规方法终止Tomcat shutdown进程,我们可以尝试使用`kill`命令(在UNIX或Linux系统上)或`taskkill`命令(在Windows系统上)来强制关闭该进程。这需要在命令行中执行相应的命令,并提供Tomcat shutdown进程进程ID。 3. 检查Tomcat日志文件:查看Tomcat服务器的日志文件,例如catalina.out或catalina.log,以获取关于无法正常关闭的任务或线程的更多信息。根据日志中的报错信息,可以尝试修复相关的问题,并重新启动Tomcat服务器。 总的来说,当Tomcat shutdown进程仍然存在时,我们可以尝试通过手动终止相关进程、强制关闭进程以及检查Tomcat日志文件来解决问题。这些方法将帮助我们确保Tomcat服务器完全关闭,并可以重新启动或进行其他需要操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值