Tomcat shutdown执行后无法退出进程问题排查及解决
问题定位及排查
上周无意中调试程序在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()这个方法有名字叫star,那么是不是有与其匹配的方法呢?确实有的一个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释放资源即可。
Tomcat shutdown执行后无法退出进程问题排查及解决的更多相关文章
- linux tomcat shutdown.sh 有时不能结束进程,使用如下指令进度重启
ps -ef | grep tomcat | grep -v grep | cut -c 9-15 | xargs kill -9 & ./startup.sh
- 外部调用Tomcat启动脚本后日志中文显示乱码问题的解决
外部sh脚本如下 #!/bin/bash while read LINE do echo "Hello $LINE!" case $LINE in all) tail -f -n2 ...
- 项目部署到tomcat Root中后导致 WebApplicationContext 初始化两次的解决方法
上一篇文章刚说项目部署到tomcat的ROOT中,今天就发现一个问题.通过eclipse启动tomcat时候,WebApplicationContext 初始化两次: 现象: 通过eclipse控 ...
- linux下tomcat shutdown后 java进程依然存在
今天遇到一个非常奇怪的问题,如标题所看到的: linux下(之所以强调linux下,是由于在windows下正常),运行tomcat ./shutdown.sh 后,尽管tomcat服务不能正常訪问了 ...
- Jenkins-ssh远程执行nohup- java无法退出
一,初步 #执行方式 ssh 192.168.2.103 " nohup java -jar /home/a/ipf/ight/feedback/ixxxedback-platform-1. ...
- Linux centosVMware运行告警系统、分发系统-expect讲解、自动远程登录后,执行命令并退出、expect脚本传递参数、expect脚本同步文件、指定host和要同步的文件、shell项目-分发系统-构建文件分发系统、分发系统-命令批量执行
一运行告警系统 创建一个任务计划crontab -e 每一分钟都执行一次 调试时把主脚本里边log先注释掉 再次执行 没有发现502文件说明执行成功了,每日有错误,本机IP 负载不高 二.分发系统-e ...
- Docker - 避免启动container后运行shell脚本执行完成后docker退出container
问题 最近在使用 Dockerfile 启动容器,发现使用Dockerfile调用容器里面的shell,当shell执行完成以后,docker会退出容器. 分析 Docker 在执行shell的时候, ...
- linux下Tomcat shutdown无效
问题: linux下Tomcat shutdown无效 linux下关闭tomcat后,发现重新启动Tomcat后.port号提示被占用, 原因: 这时可能是项目中的后台线程或者socket依旧在执行 ...
- linux上监控tomcat down掉后自动重启tomcat
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px "Helvetica Neue"; color: #454545 } p. ...
随机推荐
- UniqueIdentifier 数据类型 和 GUID 生成函数
UniqueIdentifier 数据类型用于存储GUID的值,占用16Byte. SQL Server将UniqueIdentifier存储为16字节的二进制数值,Binary(16),按照特定的格 ...
- 【云知道】LoadRunner 录制问题集锦
关键词:各路录制小白汇集于此 虽然知道君对录制不感冒,但总是看到扎堆的人说这些问题,忍不住要站出来了. 百度虽好,帮助了很多小白,但关键是百度并没有排除错误内容,经过历史的几年传播,错的都快变对的了, ...
- 搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 (1)
搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 原文地址(英文):http://www.networkcomms.net/creating ...
- 分享两个BPM配置小技巧
1.小技巧 流程图修改后发布的话版本号会+1,修改次数多了之后可能会导致版本号很高,这个时候可以将流程导出,然后删除对应的流程包再导入,发布数据模型和流程图之后,版本清零 2.小技巧 有的同事入职后使 ...
- 如何区别char与varchar?
1.varchar与char两个数据类型用于存储字符串长度小于255的字符,MySQL5.0之前是varchar支持最大255.比如向一个长度为40个字符的字段中输入一个为10个字符的数据.使用var ...
- Linux常用命令
命令格式与目录处理命令 ls 命令格式与目录处理命令 ls 命令格式:命令 [-选项][参数] 例:ls -la /etc 说明: 1)个别命令使用不遵循格式 2)当有多个选项时,可以写在一起 3)简 ...
- iOS9支付宝无法调起客户端
1.为了适配 iOS9.0 中的 App Transport Security(ATS)对 http 的限制,这里需要对 支付宝的请求地址 alipay.com 做例外,在 app 对应的 info. ...
- EF里Guid类型数据的自增长、时间戳和复杂类型的用法
通过前两章Lodging和Destination类的演示,大家肯定基本了解Code First是怎么玩的了,本章继续演示一些很实用的东西.文章的开头提示下:提供的demo为了后面演示效果,前面代码有些 ...
- 技术笔记:Indy IdSMTP支持腾讯QQ邮箱邮件发送
1.腾讯QQ邮箱的授权码问题 因为腾讯邮箱折腾了个底朝天,其要搞什么授权码登录第三方客户端,否则会报这个错误: 'Error: 请使用授权码登录.详情请看: http://service.mail.q ...
- Membership三步曲之进阶篇 - 深入剖析Provider Model
Membership 三步曲之进阶篇 - 深入剖析Provider Model 本文的目标是让每一个人都知道Provider Model 是什么,并且能灵活的在自己的项目中使用它. Membershi ...