多线程内存溢出问题解决之java.lang.OutOfMemoryError: unable to create new native thread
1,背景
最近在写一个爬虫程序,其中用到了多线程编程。令我郁闷的是哥写的代码居然有bug,平均运行2天左右就会遇到一次OOM。刚开始,事情比较多,没有精力去定位解决问题,只好每次OOM后花几分钟时间重启下服务。持续了几次之后,我就失去了耐性,这种手工重启的方式,不能忍了,明显不是我的风格(话说把程序写成这样,进而这样解决问题,太弱了)。所以在一个夜深人静的晚上,终于花了30分钟解决了这个问题。
2,解决问题的思路
2.1,立足从根本上解决问题
我所在的团队有个原则,必须从根本上解决问题。所以我初步思路就是,不用纠结怎么续命的方案,比如扩大jvm内存、修改系统连接数等等。诚然,这些方法有一定的作用,但是我认为这不是解决问题的根本之道。通过这些方法,最多是让OOM的时间延长而已,没有从根本上解决,最终还是程序崩溃的。
通过以下命令发现,确实是程序运行中创建了大量线程且没有释放(我这有3千多,虽说不多,但也不少了……)
ps -eLf | wc -l
3413
2.2,理清思路
由于程序中,我已经用了线程池技术,理论上是不会再出现由于启动的线程过多而导致的连接数不够用、内存溢出问题的。所以初步定位要么是JDK的bug、要么是自己的线程池用法有问题。
2.3,排除JDK 线程池管理的BUG问题
排除方式也很简单,用的ThreadPoolExecutor 和 OutOfMemoryError: unable to create new native thread 组合关键词,或者newFixedThreadPool 和 OutOfMemoryError: unable to create new native thread组合关键词,在google和百度上搜索。发现基本没有相关内容,所以基本排除了是JDK自身的问题。搜索发现的OOM问题排查、解决的文章很多,深度深浅不一,明显都不适合我的问题。
附程序中遇到的异常信息如下:
java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method) ~[?:1.8.0_121]
at java.lang.Thread.start(Thread.java:714) ~[?:1.8.0_121]
at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:950) ~[?:1.8.0_121]
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1357) ~[?:1.8.0_121]
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134) ~[?:1.8.0_121]
2.4,代码Review 发现写法错误
基本排除了外部原因,只能从自身找问题了。直觉告诉我,线程池没有生效,应该是生命周期的问题。所以查看了线程池的初始化代码,果然是有问题的:
ExecutorService pool4UpdateWebSiteInfo = Executors.newFixedThreadPool(2);
请注意,这里是非静态方法,也就是说每个class实例,都会初试化一次线程池。由于我的爬虫任务,每个子任务都是要创建一个class实例,导致线程池压根就没有发挥作用。所以直接将这个变量改为静态变量,部署测试,轻松解决。
static ExecutorService pool4UpdateWebSiteInfo = Executors.newFixedThreadPool(2);
2.5,效果
程序运行一段时间后,进程数很稳定,一直维护在一个较低的水平,如下:
ps -eLf | wc -l
593
3,相关知识点
3.1,查看系统设置的当前用户最大连接数
#查看进程资源限制
ulimit –u
#查看更多详情的话,可以用这个命令
ulimit -a
3.2,查看系统现在的线程数命令
ps -eLf | wc -l
通过ulimit -u命令和ps -eLf | wc -l 命令查出来的数据,就可以看到自己的程序离崩溃还有多久。
以上就是本次问题排查的全部过程。