一个进程最多可以创建多少线程,或 一台 Java 服务器最多能创建多少线程?
此问题涉及的知识点非常广且深:操作系统的内存管理,内核参数,进程,线程,栈空间等。
一台服务器可能会部署多个应用服务,需要根据服务器的硬件配置对操作系统内核参数进行调优,以发挥服务器最大性能。最基础的涉及到进程数、线程数、内存空间、线程栈空间等参数。
内存空间
操作系统把内存空间有有划分为 内核空间和用户空间。内核空间分配和管理比较复杂,这里不细说。
32 位的 Windows 默认内存分配是系统占 2G,用户占 2G。
在 Linux 操作系统中,虚拟地址空间的部又分为 内核空间 和 用户空间两部分,不同位数的系统,地址空间的范围也不同。例如,32位和64位的 Linux 系统内存空间分配如下:
Linux 32 位系统的内核空间占 1G,位于最高处,剩下的 3G 是用户空间。
如果线程栈空间使用系统默认的 8M,那么一个进程最多只能创建 384 个左右的线程。384 = 3 * 1024 / 8。
若要创建更多的线程,可以使用
ulimit -s 1024
(1M栈空间)调整创建线程时分配的栈空间大小。Linux 64 位系统的内核空间和用户空间都是 128T,分别占据整个内存空间的最高和最低处,剩下的中间部分未定义。
如果线程栈空间使用系统默认的 8M,则可创建的最大线程数 = 128 1024 1024 / 8 = 134217728,超过 1亿3千万个线程数,即可理解为创建线程已不受虚拟内存大小的限制,而是受系统的参数或性能限制。
线程栈空间
Linux 操作系统默认分配的线程栈空间大小(stack size)为 8 M
- 可通过
ulimit -a
命令查看。 - 可通过
ulimit -s
命令设置栈空间大小,单位:kbytes,重启后会失效。
[root@gxcentos ~]# ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 7143
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 65535
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 7143
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
[root@gxcentos ~]# ulimit -s
8192
[root@gxcentos ~]# ulimit -s 1024
[root@gxcentos ~]# ulimit -s
1024
Java 运行环境,通过命令查看 JVM 的内存配置时,并没有 -Xss 和 -XX:ThreadStackSize 的配置,通常不会手动设置这两个参数,JVM 会有默认值,即 JVM 创建线程时分配给线程栈空间大小 1M。可以通过 -Xss
设置线程栈空间大小。
查看 JVM 线程栈空间大小:
[root@localhost ~]# jinfo -flag ThreadStackSize 10088
-XX:ThreadStackSize=1024
[root@localhost ~]# java -XX:+PrintFlagsFinal -version | grep ThreadStackSize
intx CompilerThreadStackSize = 0 {pd product}
intx ThreadStackSize = 1024 {pd product}
intx VMThreadStackSize = 1024 {pd product}
java version "1.8.0_241"
Java(TM) SE Runtime Environment (build 1.8.0_241-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode)
设置线程栈空间大小:
# Spring Boot Jar 应用
java -Xms1024m -Xmx1024m -Xss512k
# Tomcat 应用,在 catalina 文件头部添加下面参数
JAVA_OPTS="-Xms256m -Xmx1024m -Xss512k"
JVM 在不同操作系统的默认栈空间:
操作系统 | 32位 | 64位 |
---|---|---|
Linux | 320KB | 1MB |
macOS | N/A | 1MB |
Solaris Sparc | 512KB | 1MB |
Solaris X86 | 320KB | 1MB |
Windows | 320KB | 1MB |
JVM 创建线程实质是要调系统函数来创建线程(Native Thread),操作系统需要为其分配一个线程栈空间。
线程栈空间内存不属于 JVM 运行内存,堆内存,直接内存,而是属于堆外内存,是向操作系统申请的,是操作系统剩余的可用内存。
即操作系统的可用内存决定了能创建的线程数,如果内存不够申请需要,创建线程时会报:java.lang.OutOfMemoryError:unable to create new native thread 错误。
如果是线程栈空间太小不够使用,则会报:java.lang.StackOverflowError 错误,即-Xss
设置的太小。
线程数量
Linux 系统,可通过 top -H
命令查看系统已创建的线程数,Threads 值就是总的线程数,如下:
[root@localhost ~]# top -H
top - 14:05:55 up 12:46, 3 users, load average: 0.77, 0.22, 0.11
Threads: 510 total, 1 running, 509 sleeping, 0 stopped, 0 zombie
%Cpu(s): 24.1 us, 9.8 sy, 0.0 ni, 1.7 id, 63.7 wa, 0.0 hi, 0.7 si, 0.0 st
KiB Mem : 1863004 total, 158024 free, 952660 used, 752320 buff/cache
KiB Swap: 2097148 total, 2090172 free, 6976 used. 717548 avail Mem
Linux 操作系统分配给进程的虚拟内存空间是受用户内存空间内存限制的,同样对一个进程下能创建的线程数量也是有限制的,不能无限地增多。可以总结出能创建多少线程与进程的虚拟内存上限和系统参数限制有关。
- 进程的虚拟内存空间上限:操作系统创建线程,需要为其分配栈空间,创建的线程数越多,需要的栈空间就越大,就会占用越多的虚拟内存。
- 系统参数限制:Linux 内核没有参数来对单个进程的线程数进行控制,但有系统级别的参数来控制整个系统的最大线程数。
每个进程有且只能使用它自己独立的内存空间,进程间互不干扰。同一进程的线程共同享有进程占有的资源和内存空间的。进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位。
在 64 位 Linux 系统环境下,可以通过如下公式计算得出最大的 Java 线程数量。
Java Thread Number = (MaxProcessMemory - JVMMemory - ReservedOsMemory)/ ThreadStackSize
MaxProcessMemory:一个进程的最大内存,Linux 默认是 8G,可通过修改 max_map_count 值来调整大小。
JVMMemory:JVM 的内存。
ReservedOsMemory:系统预留内存,通常为几十兆(44M,66M),Linux 可通过命令查看
[root@localhost ~]# sysctl -a|grep vm.min_free_kbytes vm.min_free_kbytes = 45056 [root@localhost ~]# cat /proc/sys/vm/min_free_kbytes 45056
ThreadStackSize:Java 线程栈空间大小,可通过
-Xss
设置。
示例:Linux 系统进程默认可用最大内存 8GB,JVM 堆分配了 1GB,使用 -Xss 默认值(1M),系统预留内存 66M。
Java线程数 = (8G - 1G - 66M)/ 1M = 7102
注意:以上计算是没有考滤系统限制,存内存计算,实际还受系统参数限制。
相关限制参数
Linux 系统对进程和线程数量的限制主要有以下几个参数:
/proc/sys/kernel/pid_max:系统全局的 PID 号数值的限制,每一个进程或线程都有 ID,ID的值超过这个数,进程或线程创建就会失败,默认值是 32678,即两个字节。
/proc/sys/kernel/threads-max:表示系统支持的最大线程数,默认值是 14553。
/proc/sys/vm/max_map_count:一个进程可能拥有的最大内存映射区域(VMA:虚拟内存区域)数。虚拟内存区域是一个连续的虚拟地址空间区域。如果默认值为:65530。
其中每 128KB 系统内存大约为 1 个内存映射区域,65530 个内存映射区域约为 8G 的内存,即一个进程最大可能拥有 8G 的内存。(引用:Increase max_map_count Kernel Parameter (Linux).htm) ,网上还没找到该参数的源码解析,官方的英文描述极为抽象不好理解)
该默认值可能偏小,或调整后的值仍不够使用时,创建线程会失败,会报:
MAX VIRTUAL MEMORY AREAS VM.MAX_MAP_COUNT [65530] IS TOO LOW, INCREASE TO AT LEAST [262144]
的错误。即该值会影响一个进程所能使用的最大内存空间,也就间接限制了能创建线程个数的上限。例如,部署了 Elasticsearch 服务,可能就需要调整该值。
max user processes:Linux 系统对每个用户的最大Processes(进程+线程)有限制,默认为 threads-max 的一半,可通过
ulimit -u
命令查看,ulimit -a
命令结果的max user processes
项值。例如,Linux 服务器部署了 MySQL 服务,如果 MySQL 用户的最大 Processes 值小于 MySQL 的最大连接数
max_connections
,同时创建的连接数大于 Processes 又小于 MySQL 的最大连接数,则会报:Can’t create a new thread (errno 11); if you are not out of available memory, you can consult the manual for a possible OS-dependent bug 的错误,这是无法再创建新的 Process 了。如果MySQL创建连接数超过应用设置的最大值, 会报:has more than ‘max_user_connections’ active connections 的错误。
通常修改用户的 Processes 最大值 大于 应用服务的最大连接数。
查看修改限制参数
查看限制参数
# 查看pid_max值 [root@gxcentos ~]# cat /proc/sys/kernel/pid_max 131072 [root@gxcentos ~]# sysctl -a|grep pid_max kernel.pid_max = 131072 # 查看threads-max值 [root@localhost ~]# cat /proc/sys/kernel/threads-max 13565 [root@localhost ~]# sysctl -a|grep threads-max kernel.threads-max = 13565 # 查看max_map_count值 [root@localhost ~]# cat /proc/sys/vm/max_map_count 65530 [root@localhost ~]# sysctl -a|grep max_map_count vm.max_map_count = 65530 # 默认的线程栈大小 [root@localhost ~]# ulimit -s 8192 # 每个用户的最大Processes数,默认为 threads-max 的一半 [root@localhost ~]# ulimit -u 6782
修改限制参数
永久修改,修改配置文件
/etc/sysctl.conf
,在文件尾添加参数。# 最大PID号值 kernel.pid_max = 131075 # 最大线程数 kernel.threads-max = 13566 #vm.max_map_count=map_count, 32G该值为262144, 256G该值为2097152 vm.max_map_count = 262144
执行生效命令
[root@gxcentos ~]# sysctl -p kernel.pid_max = 131075 kernel.threads-max = 13566 vm.max_map_count = 262144
查看修改结果
# 修改前的值 [root@gxcentos ~]# cat /proc/sys/kernel/pid_max 131072 [root@gxcentos ~]# cat /proc/sys/kernel/threads-max 14287 [root@gxcentos ~]# cat /proc/sys/vm/max_map_count 65530 # 编辑文件修改 [root@gxcentos ~]# vim /etc/sysctl.conf # 使修改生效 [root@gxcentos ~]# sysctl -p kernel.pid_max = 131075 kernel.threads-max = 13566 vm.max_map_count = 262144 # 修改后生效的值 [root@gxcentos ~]# cat /proc/sys/kernel/pid_max 131075 [root@gxcentos ~]# cat /proc/sys/kernel/threads-max 13566 [root@gxcentos ~]# cat /proc/sys/vm/max_map_count 262144
临时修改,使用命令修改,重启后会失效
[root@localhost ~]# sysctl -w kernel.pid_max=65535 [root@localhost ~]# echo 65535 > /proc/sys/kernel/pid_max [root@localhost ~]# sysctl -w kernel.threads-max=13566 [root@localhost ~]# echo 13566 > /proc/sys/kernel/threads-max [root@localhost ~]# sysctl -w vm.max_map_count=262144 [root@localhost ~]# echo 262144 > /proc/sys/vm/max_map_count
修改最大文件句柄数
查看操作系统允许打开的最大句柄数(文件描述符)
[root@localhost ~]# ulimit -n 1024 # open files [root@localhost ~]# ulimit -a ...省略... open files (-n) 1024 .........
修改允许打开的文件句柄上限数。编辑
/etc/security/limits.conf
文件,修改限制数。# End of file root soft nofile 65535 root hard nofile 65535 * soft nofile 65535 * hard nofile 65535
退出当前登录终端,重新登录,不需要重启系统。
快速修改
echo "* soft nofile 65536" >> /etc/security/limits.conf echo "* hard nofile 65536" >> /etc/security/limits.conf
修改运行中的进程的打开文件句柄限制数
# CentOS7系统使用命令 prlimit --nofile=65536:65536 --pid 39977 # CentOS6系统使用命令 echo - n "Max open files=65535:65535" > /proc/39977/limits # pidof mysqld 是返回进程id号 echo - n "Max open files=65535:65535" > /proc/`pidof mysqld`/limits
修改用户最大Processes数
Linux 系统对每个用户的最大Processes(进程/线程)有限制,默认为 threads-max 的一半
查看每个用户的最大进程数限制值
[root@gxcentos ~]# ulimit -u 7143 [root@gxcentos ~]# sysctl -a|grep threads-max kernel.threads-max = 14287
修改每个用户的最大进程数上限值。
编辑
/etc/security/limits.conf
文件,添加配置。# End of file * soft nproc 8192 * hard nproc 8192
编辑
/etc/security/limits.d/20-nproc.conf
(CentOS 7),修改里面的值,没有则添加* soft nproc 8192 * hard nproc 8192 root soft nproc unlimited
退出当前登录终端,重新登录,不需要重启系统。
切换不同的用户,执行
ulimit -u
命令查看用户的最大进程数上限是否已修改生效。动态修改运行中服务的限制。
Linux 系统为每个进程都在
/proc
目录下创建一个以进程号(PID)为名的目录,目录中有limits
文件(/proc/{pid}/limits
),该文件加载的是当前进程的相关限制参数。[root@localhost ~]# pidof redis-server 28614 [root@localhost ~]# cat /proc/28614/limits Limit Soft Limit Hard Limit Units Max cpu time unlimited unlimited seconds Max file size unlimited unlimited bytes Max data size unlimited unlimited bytes Max stack size 8388608 unlimited bytes Max core file size unlimited unlimited bytes Max resident set unlimited unlimited bytes Max processes 6782 6782 processes Max open files 65535 65535 files Max locked memory 16777216 16777216 bytes Max address space unlimited unlimited bytes Max file locks unlimited unlimited locks Max pending signals 6782 6782 signals Max msgqueue size 819200 819200 bytes Max nice priority 0 0 Max realtime priority 0 0 Max realtime timeout unlimited unlimited us
若是生产环境已启动不能随便重启,动态修改参数是不生效的。可执行下面命令使立即生效,如下。
# CentOS7系统使用命令 prlimit --nproc=65536:65536 --pid 39977 # CentOS6系统使用命令 echo - n "Max processes=65535:65535" > /proc/39977/limits # pidof mysqld 是返回进程名的id号 echo - n "Max processes=65535:65535" > /proc/`pidof mysqld`/limits
相关资料
注意:本文归作者所有,未经作者允许,不得转载