线上经常有项目发生oom,而且oom是由操作系统抛出,而非jvm抛出。
找了一个经常oom的项目实例,进行分析:

jvm参数配置并无太大问题,jvm监控中堆内存,元空间都不大,非堆也不大,但是jvm线程数不正常,达到了几千个,最高时达到16k,且线程数是稳步增长的。
得知某个功能使用了线程池进行业务操作。观察代码初步判断,问题出在线程池使用不当。
针对上面排查,写了一个简化的模拟程序(仅仅模拟线程池使用,省略业务处理),验证判断。
@GetMapping("/testThread")
public String testThreadPool() {
ExecutorService executor = Executors.newFixedThreadPool(100, ThreadFactoryBuilder.create().setNamePrefix("test-leak-").build());
for (int i = 0; i < 1000; i++) {
executor.submit(() -> {
logger.info("test");
});
}
return "success";
}
说明:这个逻辑写在controller中,get方法,每次执行时会创建一个线程池,用完并没有shutdown。

从下图可以看见,叫test-leak的线程,一共有500个。

业务代码在方法的执行过程中,new了一个线程池,并使用该线程池执行业务,在方法执行完毕后,并未执行shutDown方法,结束线程。这会导致内存泄漏,创建出来的线程,并不能被jvm回收掉。而业务方法的入口,是一个定时任务,所以,每执行一次都会导致线程数增加,所以我们能从监控中看到线程数不断稳步增长。
其它几个小知识:
A:因为线程池中有活跃的线程,这个线程会有引用指向当前线程池对象,而thread本身时java gc 的root对象。这就导致了,垃圾收集器在计算可达对象时,这个线程池始终有引用指向它(可达到),所以线程池对象不会被回收,而里面的线程也不会被回收。
A:普通的thread对象,start以后开始执行,执行完以后自动就会销毁。但是线程池里的线程不会自动被销毁,只有shutdown以后才行。