最近一个项目上线后,服务器磁盘两三天报一下磁盘100%的异常,开始以为是磁盘问题,因为这个服务器上有一个rsync的定时任务,10分钟一次,用于同步静态文件,大概1万个html文件,正常不到1分钟同步完成。
但是运维帮忙重启后,两三天磁盘又报警,看了一下内存使用,内存从重启后,一直缓慢的增加,没有回落,直到报警前的90%以上。看了一下监控系统统计的线程数,发现线程数从上次重启后,一次在增加,没有稳定在一个值范围。正常使用线程池,这个应该是在一个固定值左右小范围波动的。但是报警的服务器,线程数一直在增加。
登上服务器,用jstack -l pid > jstack.log 看了一下内存线程的情况,统记了一下线程的状态信息,发现WAITING(parking)的线程数有13000多个(使用统计状态的命令:grep --color=auto -A2 -B2 '.Thread.State:' jstack.log | awk -F ".Thread.State:" '{print $2}' | awk -F "Unsafe.park" '{print $1}' | sort | uniq -c | sort -k1,1nr )。自此,问题应该初步锁定是线程出了问题。
查看代码,发现一个类中使用了ExecutorService executorService = Executors.newFixedThreadPool(20);,但是没有使用static,导致这个类每次创建时,都会创建一次线程池,而之前的创建的线程又没有销毁,从而没有销毁的线程一直占有着内存,而且服务器上日志开始有大量的java.lang.OutOfMemoryError: unable to create new native thread错误。
那为什么java线程没有销毁,会导致服务器内存达到100%呢?
因为在java语言里, 当你创建一个线程的时候,虚拟机会在JVM内存创建一个Thread对象同时创建一个操作系统线程,而这个系统线程的内存用的不是JVMMemory,而是系统中剩下的内存(MaxProcessMemory - JVMMemory - ReservedOsMemory)。 其中MaxProcessMemory指的是一个进程的最大内存,JVMMemory是JVM内存,ReservedOsMemory是保留的操作系统内存,ThreadStackSize是线程栈的大小。
所以在使用static ExecutorService executorService = new ThreadPoolExecutor(20, 40, 60L,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(4000));修改了相关的线程使用后,程序目前稳定了。