一次tomcat内存溢出的排错经历《二》
上篇文章粗略的写了解决问题的大致过程。这次使用jvisualvm.exe,来还原程序出错的具体过程。
jvisualvm.exe是JDK自带的工具,在JDK的bin目录下可以直接找到,双击打开即可,界面如下:
上次发现是AsyncManager$1
这个类存在问题,从类名上看这是个匿名内部类,可以发现主类是AsyncManager
AsyncManager.java
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
public class AsyncManager {
//单例模式 设计模式
private AsyncManager(){
}
//内存中 只有 一个 AsyncManager对象存在
private final static AsyncManager asyncManager = new AsyncManager();
public static AsyncManager getInstance(){
return asyncManager;
}
//声明 一个变量
public ExecutorService executors;
public void execute(Runnable rb){
executors.execute(rb);
}
//初始化的方法 初始化的时候 规定好 有几个线程 产生
public void initWith(int threadCount){
//ThreadFactory 线程工厂 负责 生成线程
this.executors = Executors.newFixedThreadPool(threadCount, new ThreadFactory() {
//初始化 线程组
ThreadGroup tg = new ThreadGroup("AsyncManager");
@Override
public Thread newThread(Runnable r) {
//Thread t = new Thread(tg,r);
return new Thread(tg,r);
}
});
}
}
使用全局搜索找到使用这个类的代码
发现调用的方式是这样的。
AsyncManager.getInstance().initWith(50);
AsyncManager.getInstance().execute(new Runnable(){})
根据业务,这部分的代码会经常调用,我们来模拟
import java.util.Timer;
import java.util.TimerTask;
import com.dragonsoft.commons.terminalPassageway.AsyncManager;
import com.sun.management.OperatingSystemMXBean;
public class ThreadTest {
public static void main(String[] args) {
Timer timer = new Timer();
//使用定时器来模拟请求调用改代码
timer.schedule(new TimerTask() {
public void run() {
AsyncManager.getInstance().initWith(50);
AsyncManager.getInstance().execute(new Runnable(){
@Override
public void run(){
System.out.println(1);
}
});
}
}, 2000 , 1000);
}}
运行Main方法后,我们打开之前说的软件,就会看到进程了,双击打开
这是与线程有关的类,所以我们直接去看线程相关信息。
通过动图,我们了解到实时线程一直在在创建,按道理说线程生命周期是执行完之后,就会消亡。我们这里的线程的任务是输出1即可。所有没有别的任务。
但是不停的创建线程就有问题了。仔细观察,执行完任务的线程都处于等待状态,而不是消亡。问题的真相越来越近了。
观察代码创建线程池的代码是这句
this.executors = Executors.newFixedThreadPool();
查看JDK文档,关于newFixedThreadPool
的解释如下
创建一个可重用固定线程数的线程池
,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待
。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。
也就是说,创建完这个线程后不会自动消失,而是一直处于等待状态,也就很好的解释刚才的那种现象了。
因此就有了下面的解决方案。
参考地址:https://blog.csdn.net/clementad/article/details/75663956
当然,上面的解决方案是我在解决完问题之后和同事交流得到的。
自己在看到这种现象之后,首先想到的是执行完线程之后关闭不就解决了,因此查看这个类的源码
嘿嘿,找到了。也就是shutdown方法,加进去,看看效果
题外话(如不了解,自行百度。看不懂英文不怪我,要是懂英文直接查看源码文档是最好的解释!):
加上上面的这句话,继续执行方法:
从控制台可以看到任务一直在执行,但是线程却没有一直增加。问题完美解决。
另一种解决方案:
也就是上面博客提到的解决方案,自我感觉第二种解决方案会更好,初始化一次线程池,重复利用就行了,这样可以大量的减少系统的开销没节约了线程创建,消亡的步骤,想想Spring容器的设计理念,不正是这样的吗?还有某某连接池,都是这种设计。