五分钟彻底解决 OutOfMemoryError: Unable to create native threads

作为 Java 程序员,我们几乎都会碰到 java.lang.OutOfMemoryError 异常。

JVM 在抛出 java.lang.OutOfMemoryError 时,除了会打印出一行描述信息,还会打印堆栈跟踪,因此我们可以通过这些信息来找到导致异常的原因。

其中一种异常是 java.lang.OutOfMemoryError: Unable to create native threads,我们通过这篇文章来彻底搞懂它。

抛出这个异常的过程大概是这样的:

  1. Java 程序向 JVM 请求创建一个新的 Java 线程。
  2. JVM 本地代码(Native Code)代理该请求,通过调用操作系统 API 去创建一个操作系统级别的线程 Native Thread。
  3. 操作系统尝试创建一个新的 Native Thread,需要同时分配一些内存给该线程,每一个 Native Thread 都有一个线程栈,线程栈的大小由 JVM 参数-Xss决定。
  4. 由于各种原因,操作系统创建新的线程可能会失败,下面会详细谈到。
  5. JVM 抛出 java.lang.OutOfMemoryError: Unable to create new native thread 错误。

因此关键在于第四步线程创建失败,JVM 就会抛出 OutOfMemoryError,那具体有哪些因素会导致线程创建失败呢?

  1. JVM 内存大小限制
  2. ulimit -u 限制
  3. 参数 sys.kernel.threads-max 限制
  4. 参数 sys.kernel.pid_max 限制

第一种失败原因简单说一下:Java 创建一个线程需要消耗一定的栈空间,并通过-Xss参数指定。需要注意的是栈空间如果过小,可能会导致 StackOverflowError,尤其是在递归调用的情况下,但是栈空间过大会占用过多内存。

同时还要注意,对于一个 32 位 Java 应用来说,用户进程空间是 4GB,内核占用 1GB,那么用户空间就剩下 3GB,因此它能创建的线程数大致可以通过这个公式算出来:

1
Max memory(3GB) = [-Xmx] + [-XX:MaxMetaSpaceSize] + number_of_threads * [-Xss]

不过对于 64 位的应用,由于虚拟进程空间近乎无限大,因此不会因为线程栈过大而耗尽虚拟地址空间。但是请你注意,64 位的 Java 进程能分配的最大内存数仍然受物理内存大小的限制。

下边重点来介绍后边三种失败因素。

在搞明白 pid_maxulimit -uthread_max 的区别前,需要先明白进程和线程之间的区别。

一个最典型的区别是,(同一个进程内的)线程运行时共享内存空间,而进程在独立的内存空间中运行。

pid_max

pid_max 参数表示系统全局的 PID 号数值的限制,每一个进程都有 ID,ID 的值超过这个数,进程就会创建失败,pid_max 参数可以通过以下命令查看:

1
cat /proc/sys/kernel/pid_max

默认情况下,执行以上命令返回 32768,这意味着我们可以在系统中同时运行 32768进程,这些进程可以在独立的内存空间中运行。

修改方法

echo 65535 > /proc/sys/kernel/pid_max

上面只是临时生效,重启机器后会失效

永久生效方法:

/etc/sysctl.conf 中添加 kernel.pid_max = 65535

1
2
vi /etc/sysctl.conf
kernel.pid_max = 65535

或者:

1
echo "kernel.pid_max = 65535" >> /etc/sysctl.conf

threads-max

treamd-max 用来限制操作系统全局的线程数,通过以下命令查看 treamd-max 参数:

1
cat /proc/sys/kernel/threads-max

上边的命令返回 126406,这意味着我可以在共享内存空间中拥有 126406线程

ulimit -u

limit -u 表示当前用户可以运行的最大进程数

这个值怎么来的

root 账号下 ulimit -u 得到的值默认是 cat /proc/sys/kernel/threads-max 的值 / 2,即系统线程数的一半。

普通账号下 ulimit -u 得到的值默认是 /etc/security/limits.d/20-nproc.conf文件中指定的:

修改方式

1
ulimit -u 65535

ulimit 受到全局限制

ulimit -u 的值受全局的 kernel.pid_max 的值限制。也就是说如果 kernel.pid_max=1024,那么即使你的 ulimit -u 的值是 63203,用户能打开的最大进程数还是 1024。