最近项目的系统突然出现崩溃,无法响应请求。使用运维账号登陆服务器也无法登陆,提示

1
2
fork: retry: No child processes 
fork: Resource temporarily unavailable

通过root账号登陆上去查看应用日志,报错如下:

1
java.lang.OutOfMemoryError: unable to create new native thread

OOM造成无法创建线程。于是先把部分服务停了,运维账号则可正常登陆。

分析原因

由于之前也没有遇到过类似问题,通过网上查找,分析了多种原因,记录如下。

应用内存分配过多

由于该项目是一个微服务构架,总共有近20个微服务,但只用了两台64G内存的服务器做集群,内存确实是很紧张。我统计了所有服务分配的堆内存,即 -Xmx 的值,一共分配了大概45G的样子。因此决定重新分配各微服务的堆内存大小。

1、将不必要的微服务停止

2、将不使用量不大的部分服务减少堆内存的分配的大小

3、将之前分配过大的堆内存减小

调整之后所有启用的微服务分配的总堆内存大概减小到了32G,之后重启各服务,以为万事大吉了,但过了几天再次出现无法创建线程的问题了。。。

用户可用线程数限制

这次怀疑是用户最大的可用线程数受限了,通过 ulimit -u 命令可查询当前用户最大可使用的线程数。经查询运维账号可使用的线程数为 65535 这个数量完全是够用了。

我们可以通过这个命令统计出某个进程所使用的线程数,所有服务的线程数总合完全没有超过 65536

1
ps Hh p <pid> | wc -l

因此可排除这个原因

线程数过高

经常上面再次分析没有找到原因,但通过查看各服务线程数,发现某个服务线程数很高,停止或重启该服务,则free内存多了很多,但过几天后随着该服务线程数越来越高,free 内存也越来越少。

因此决定通过分析该服务线程数过高看能否找出问题。

1、首先可以通过jps命令查看当前用户下的所有java应用进程,得到各服务的pid

2、通过 ps Hh p <pid> | wc -l 查询每个pid,找出线程数最多的一个服务

3、通过top Hp <pid> 可以动态展示该进程下的线程情况。找出最占CPU的线程,记录该线程的pid

某进程下的线程情况

4、通过 printf “%x” <pid> 将上面的线程pid转化为十六进制数

5、通过Jstack 查找该线程的堆栈信息

1
jstack <进程pid> | grep <线程的十六进制数形式>
查询线程堆栈信息

jstack -l 表示显示锁信息

grep -C 5 表示展示上下5行信息

通过对堆栈的分析,我们可能获得一些信息,如该线程是什么状态,被什么锁了等。

但很遗憾,通过对堆栈分析,我也没有找出具体的问题。

Linux缓存

通过使用free -m 命令,发现虽然free内存很少,但buff/cache占用很大

free 情况

按理来说 buff/cache也是空闲内存,如果系统内存不足,应该是会自动清缓存的。很显然系统并没有清除缓存,所有导致系统可用的空闲内存不足。

首先分析缓存占用大的问题。应用系统中有许多附件需要上传或下载,并且对minio做了群集配置,这可能是造成cache过大的原因。

1、手动清除缓存

Linux中可以手动清除缓存,通过 修改/proc/sys/vm/drop_caches 文件来触发。

这个文件默认值为0

值为1时:可以释放pagecache缓存

值为2时:可以释放pagecache和inode缓存

值为3时:可以释放pagecache, dentries和inodes缓存

因此,可以通过修改这个文件的值来触发系统清理缓存。

1
2
3
4
sync		#通知系统将缓存及时写入
echo 1 >> /proc/sys/vm/drop_caches
echo 2 >> /proc/sys/vm/drop_caches
echo 3 >> /proc/sys/vm/drop_caches

执行完成后再使用 free -m 命令,会发现free内存多出来几十G,这下内存暂时是足够了。

但如果后面缓存继续占用越来越大,这种情况还是有可能发生,因此最终还是需要找出系统为什么不会自动清理缓存的原因才能从根本上解决这个问题。