记一次OutOfMemoryError的处理过程

312 阅读3分钟
背景

我们的大数据组件基于Knox做了一个统一登录的入口,早上测试发消息说Atlas页面打不开了,需要定位一下问题。首先检查Atlas服务是正常的,然后查看Knox服务,发现日志中出现这样一个警告:java.long.OutMemoryError:unable to create new native thread.这是线程资源不够了呀,于是开始排查。

逐步排查
检查用户线程数限制
su knox
ulimit -a

发现max user processes设置的是65536,说明线程数限制已经修改过了,那现在线程资源还是不够,是不是系统总的线程资源达到上限了呢?

检查系统现有线程数
top -H

发现Threads数值接近32768,比着65536还有一半余量,说明线程资源还多着呢,为什么会抛出这个异常呢?

首先有个疑问,测试环境只是安装了部分组件,没有启动什么任务,为什么会有这么多线程呢?

检查各个用户下的线程数
ps h -Led -o user | sort | uniq -c | sort -nr

发现root用户下超过2W的线程,这个就非常离谱,ps -ef | grep root输出发现有接近7000个sshd服务,说明有ssh连接没有关闭呀,为什么会有这么多连接呢,后来排查发现是一个监控服务每分钟通过ssh连接跳转到指定主机收集信息,但是没有退出连接导致的,修复之后线程使用数下来了。通过一个一个排查发现还有一个服务占用了大量线程,通过下面这个命令可以查看进程开启了多少个线程:

pstree -p PID | wc -l

这个服务开启了大量线程后没有关闭导致了线程泄露。解决完这两个问题后,服务恢复了正常,但是修复之前系统的线程资源还有一半没有用呢,为什么会导致程序创建线程失败呢?

pid_max和thread_max

在linux系统中,无论线程还是进程都需要PID标识,所以能创建的线程和进程数量都被pid_max参数限制。现有系统pid_max设置的是多少呢,通过下面的命令:

sysctl kernel.pid_max

或者直接查看内核文件:

cat /proc/sys/kernel/pid_max

可以看到现有系统pid_max参数设置为32768,现在就可以解释为什么线程会创建失败了。

pid_max是根据什么设置的

在内核文件./include/linux/thread.h中有以下配置:

#define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x80000)

CONFIG_BASE_SMALL是在编译内核时指定的参数,可以全局搜索下编译时指定的这个参数值,我这里在.config文件中找到此参数值为0,说明编译时没有赋值,那PID_MAX_DEFAULT的值就是32768.

PID_MAX_DEFAULT默认值是32768,那实际值是怎么算出来的呢?

在内核文件./include/linux/thread.h中还有下面这个配置:

#define PID_MAX_LIMIT (CONFIG_BASE_SMALL ? PAGE_SIZE * 8 : (siezeof(long) > 4 ? 4 * 1024 * 1024 : PID_MAX_DEFAULT)

这段代码的意思就是如果编译时没有指定CONFIG_BASE_SMALL,那么对于64位的操作系统来说,PID_MAX_LIMIT的值为:4 * 1024 * 1024 ,对于32位操作系统来讲,PID_MAX_LIMIT的值为:32768.

PID_MAX实际值是这样的max(PID_MAX_DEFAULT, PIDS_PER_CPU_DEFAULT * num_possible_cpus())PIDS_PER_CPU_DEFAULT值为1024,这行代码的意思就是如果cpu个数少于32,则PID_MAX的值为32768,否则是根据PIDS_PER_CPU_DEFAULT * num_possible_cpus()计算出来的。num_possible_cpus就是可用的cpu个数,也即是逻辑cpu的个数。

这里有个坑,如果你的服务器是虚拟机,那获取到的cpu个数可能是宿主机的cpu个数,可以通过dmesg | grep Allowing查看cpu个数。

修改pid_max
#临时修改
sysctl kernel.pid_max=1000000
#永久修改
echo "kernel.pid_max=1000000" >> /etc/sysctl.conf
#查看
sysctl kernel.pid_max

\