目录
方法2:在tomcat context.xml配置文件中,添加三个属性(未测试)
一、问题由来
我们组部署服务的方式,是将项目打成war包,然后拷贝到服务器tomcat的webapps目录下,虽然不需要重启tomcat,但是在log目录下catalina.out日志中会看到类似于下面的WARNING报错:
org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [DemoProject] appears to have started a thread named [com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread-#0] but has failed to stop it. This is very likely to create a memory leak.
大概意思为线程池的线程资源无法释放,容易造成内存溢出。
二、产生原因
一般情况下,如果我们重启web应用是通过重启tomcat的话,则不存在内存泄漏问题。
是什么原因导致tomcat内存泄漏的。这个要从热部署开始说起,因为tomcat提供了不必重启容器而只需重启web应用以达到热部署的功能,其实现是通过定义一个WebappClassLoader类加载器,当热部署时就将原来的类加载器废弃并重新实例化一个WebappClassLoader类加载器。但这种方式可能存在内存泄漏问题,因为ClassLoader是一个结构复杂的对象,导致它不能被GC回收的可能性比较多,除了对ClassLoader对象有引用可能导致其无法回收,还可能对其加载的元数据(方法、类、字段等)有引用都会导致无法被GC回收。
参考:https://www.jb51.net/article/101745.htm
在实际项目中,主要是这几类资源无法释放:
1,连接池
2,自己写的任务队列线程,
3,mysql的线程;
三、解决办法
方法1:新建一个context的监听器
1)如果是连接池,可以新一个context的监听器
自定义实现一个javax.servlet.ServletContextListener,重写contextDestroyed方法,然后注册到servlet中。
public class ContextDestroyedListener implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent arg0) {
System.out.println("you must be reload tomcat....please waiting ...");
CacheManager.shutdown();
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
try {
DriverManager.deregisterDriver(driver);
System.out.println("stoping.... "+driver.toString());
} catch (SQLException e) {
e.printStackTrace();
}
}
Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
Thread[] threadArray = threadSet.toArray(new Thread[threadSet.size()]);
for(Thread t:threadArray) {
if(t.getName().contains("Abandoned connection cleanup thread")||t.getName().contains("task_excutor")) {
synchronized(t) {
t.stop();
}
}
}
}
@Override
public void contextInitialized(ServletContextEvent arg0) {
}
}
在web.xml里面配置监听。
2)如果是自己的线程可以使用线程名称来关闭
if(t.getName().contains("Abandoned connection cleanup thread")||t.getName().contains("task_excutor")) {
synchronized(t) {
t.stop();
}
}
方法2:在tomcat context.xml配置文件中,添加三个属性(未测试)
maxWait="2000" //单位毫秒
removeAbandoned="true"
removeAbandonedTimeout="180" //单位秒
即增加连接池回收机制,活动连接,在超过时间内,没有使用,会被自动回收。
个人感觉这个没啥用,主要之前没用过。
<Context path="/kaka" docBase="kaka" debug="0" reloadbale="true" privileged="true">
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<Resource name="jdbc/testSiteds"
auth="Container"
type="javax.sql.DataSource"
maxActive="100"
maxIdle="30"
maxWait="10000"
username="root"
password="root"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/testSite"
maxWait="2000"
removeAbandoned="true"
removeAbandonedTimeout="180"/>
</Context>
四、模拟问题场景--多次热部署同一应用
参考:https://www.cnblogs.com/grey-wolf/p/10179895.html
1、本地环境配置
一个自己的war包,两个tomcat自带的war包,用来控制reload应用,配置好后,启动tomcat。
2、打开jconsole进行监控
点击JDK/bin 目录下面的jconsole.exe 即可启动,主要是监控线程。
3、reload应用一次
打开localhost:9080/manager/html,如果不能访问,请在tomcat下面的conf中的tomcat-users.xml配置:
<role rolename="manager-gui"/>
<user username="admin" password="admin" roles="manager-gui"/>
观察jconsole中的线程数是否增加
4、反复重试第3步
如果不出意外(程序中有线程泄漏)的话,jconsole中的线程图应该是下面这样,一步一个台阶:
5、查看tomcat下logs中的catalina.log
这里面可能会有些线程泄漏的警告,如下: