? ? 做Java应用,内存泄漏和OOM的情况遇到的比较多,线程泄漏倒还真是第一次遇到。当然,这其中很大程度上利益于使用线程池,而不是自己创建线程的方式来管理线程。
? ? 测试找过来说没法通过SecureCRT登录到某个系统的测试环境,提示没有资源可分配了。于是尝试了下,发现用部署该Java应用的账号确实没法登录,但是换用其他的账号可以登录。通过服务器上有持续打印“cannot create native thread”的错误日志,顺手top了下,发现服务器Load相对平时已经很有点高了。
? ? 从错误日志来看,Java应用请求向服务器创建线程,但是服务器因为资源限制没法提供,于是应用打印了这个错误日志。自己YY了下:有可能是服务器资源确实不足了,因为这台服务器还部署了其他应用;也可能是应用创建的线程实在是太多了。通过非部署账号可以登录到服务器,而且公司服务器配置还算不错,所以很快就放弃了第一点怀疑,开始验证是否是第二个猜测。
? ? 看了下这个Java应用创建的线程数量,不看不知道,一看吓一跳,已经创建了1023个线程了。可以使用下面三个方法来查看Java应用已经创建的线程数量,先通过ps+grep或者jps之类的取得java应用的pid。
class="Shell">ps -Tfp <pid>
cat /proc/<pid>/stat | awk '{print $20}'
ls /proc/<pid>/task | wc
? ? ?因为这个应用自己是比较熟悉的了,平时正常情况下线程数基本在450左右,1023肯定是个不正常的情况了,所以心里基本有底已经部分线程失控了。顺手ulimit -a看了下,发现max user processes被设置为1024了。虽然说这个值确实是有点小了,生产环境下基本不可能设置一个这么小的值。但测试环境下倒正好是因为这个相对较小的值才暴露出了问题,如果设置的很大,估计还得跑很久才能暴露出问题。
? ? 因为此时部署该Java应用的admin账号已经达到最大线程了,所以没法通过SecureCRT远程登录上去。此时,应该是可以找root账号做下thread dump的,问题基本就清楚了。结果测试冲动了下直接就让root把服务器重启了下,好在环境没变化,跑了一段时间之后问题就再浮现出来了。当然,系统比较熟悉的话,单次thread dump基本就可以确定问题所在了。如果系统不是太熟悉的话,记得在不同的时间点做下thread dump,便于分析出产生的主要线程是什么。
Thread Dump之后,如果问题太明显,看看dump日志基本就可以发现了。如果情况比较复杂,可以试试一个叫TDA的工具,可以看到各状态线程的数量,以及各种类型线程的数量。更有用的是,可以比较多次dump之间线程的变化情况,这个对于比较复杂的情况非常有帮助。
回到这个case,原因还是比较简单的。应用上动态创建了很多与第三方公司的客户端长连接线程,进行双方发送消息,为了进行并发流量统计和控制,在这个客户端线程又创建了一个线程用来定时清空计数。后台有定时任务定时检测各个客户端线程的运行情况,当客户端线程异常结束之后,该定时检测任务会在对应客户端线程的实例上重新new Thread(Runnable),然后start起来。问题就出在客户端线程异常结束的处理上,只是通过finally的方式结束掉了客户端线程,但并没有对清空计数的子线程进行处理,于是后台定时任务重启客户端的时候会重新创建一个清空计数的子线程。当测试环境不太稳定,客户端线程基数较大,又不断出现异常结束的情况下,后台检测客户端状态的定时任务就会大量的重启这些客户端线程,产生大量没有退出、不受控制的清空计数的子线程。
知道原因了,问题也比较简单,具体处理办法就不说了。