很多人都知道阿里的Java开发严令禁止使用Executors的方式来创建线程池,禁止的理由是“为了让开发者更加明确线程池的运行规则,更加了解线程池的底层工作原理,从而避免不规范的使用造成服务器资源耗尽的风险”,本菜鸡曾经也是在UC原生团队呆过一年,在内部的开发手册上也确实记录了很多这些Java ,mysql的规范。当时由于业务繁忙,也没深究,造成了极大的浪费,其实很矛盾,带我的那位私有云的架构师大佬每天都叫我多学习,但是本菜鸡每天都在堆砌着业务逻辑代码,加班完回家一般都九点十点了,自己偷偷懒,又不看了。于是造成了他对我很失望,也不热衷于多问他问题,觉得我很浪费身边的资源,不好学,几次告诫我以后离职的话会后悔,学习是为了自己的。现在熬不住福报,离职了,当事人心里确实非常后悔,要是再给一次重来的机会,一定把业务和学习任务齐头并进!把他脑汁都榨干了再走!现在,宝宝心里苦也是说不出…
扯远了不好意思…这篇文章不是吹水文,下面开始正题。
首先要了解什么是线程池。可以看:线程池的简单的底层解析和使用
一:通过Executors方式创建:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
二:通过ThreadPoolExecutor方式创建:
ExecutorService cachedThreadPool = new ThreadPoolExecutor(1,3,10, TimeUnit.SECONDS,arrayBlockingQueue);
第一个问题可以看出,Executors方式创建可以不需要传一些参数,那么这些参数是什么呢?我们都知道线程池要有最大线程数,核心线程数和过期时间和等待队列…那么不传,就是用的默认,那么默认的是多少呢?点进去一看!
cachedThreadPool缓存线程池中,假如不指定默认的corePoolSize最大线程数,那么因为它是无界的,就会一直根据任务来创建线程。核心线程数默认0,最大线程数2147483647.。。。二十一亿四千七百多万。很明显,我们的内存根本吃不起这个数量的默认最大线程数。
而ThreadPoolExecutor方式创建则要求我们一定要传入参数,我们就可以按照正常的最优线程数计算公式来计算我们要开启的线程。
所以这个时候,我们就可以知道,所谓的服务器资源耗尽,就是JVM内存耗尽,也称Out Of Memory(OOM)。
OOM情况一:
就是上面说的那样,Executors创建方式因为可以不传参数而使用默认值进行线程池的创建,所以在不规范使用的场景下就很容易导致无边界线程池开启了过多的线程,从而导致OOM。
多说无益,上demo:
由于我忘了调小JVM的内存分配,所以这里创建到了上千万还在创建。但是通过阿里那个Arthas工具(博客:Java系统性能监控工具Arthas的使用)可以看到线程们的执行情况:
于是通过idea临时调小了JVM内存就可以成功出现OOM:
[Full GC (Ergonomics) 4071K->4071K(5120K), 0.0133345 secs]
[Full GC (Ergonomics) 4072K->4072K(5120K), 0.0139875 secs]
[Full GC (Ergonomics) 4074K->4074K(5120K), 0.0175359 secs]
[Full GC (Ergonomics) 4075K->4058K(5120K), 0.0184669 secs]
[Full GC (Ergonomics) 4074K->4027K(5120K), 0.0227864 secs]
[Full GC (Ergonomics) 4034K->4032K(5120K), 0.0163257 secs]
[Full GC (Ergonomics) 4034K->4033K(5120K), 0.0159485 secs]
[Full GC (Ergonomics) 4034K->4033K(5120K), 0.0121078 secs]
[Full GC (Ergonomics) 4034K->4033K(5120K), 0.0149764 secs]
[Full GC (Ergonomics) 4034K->3966K(5120K), 0.0177060 secs]
[Full GC (Ergonomics) 3992K->3966K(5120K), 0.0176927 secs]
[Full GC (Ergonomics) 3992K->3970K(5120K), 0.0166102 secs]
[Full GC (Ergonomics) 3992K->3894K(5120K), 0.0252081 secs]
[Full GC (Ergonomics) 3977K->3895K(5120K), 0.0229713 secs]
[GC (Allocation Failure) -- 3977K->4089K(5120K), 0.0031258 secs]
[Full GC (Ergonomics) 4089K->3067K(5120K), 0.0323390 secs]
[Full GC (Ergonomics) 4091K->3989K(5120K), 0.0282569 secs]
[Full GC (Ergonomics) 4080K->3942K(5120K), 0.0143164 secs]
[Full GC (Ergonomics) 4086K->3934K(5120K), 0.0185123 secs]
org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [com.example.demo.DemoApplication]; nested exception is java.lang.OutOfMemoryError: GC overhead limit exceeded
OOM情况二:
那么通过ThreadPoolExecutor创建并且制定最大线程数,是否就绝对安全了呢?也不是,还有一个地方,就是BlockingQueue等待队列那里~看看demo:
由于我调小了内存,死循环中等待队列有50万的capacity容量,这个时候也会造成OOM。我这里的打印忘记改了,由于这里指定了最大线程数,所以cachedThreaPool应该是对池内线程进行复用,而不是无界限创建。
OOM情况三:
最后一种情况,其实是内存泄漏,例如池内线程去执行的任务是有高度延时的或者可能造成死锁的,导致线程无法返还池中,就会出现明明内存分配很大但是仍然无法执行任务的情况。在线程数无界和等待队列大小无界的情况配合下,也会导致OOM。