一台Java服务最多可以创建多少个线程

一个进程最多可以创建多少线程,或 一台 Java 服务器最多能创建多少线程?

此问题涉及的知识点非常广且深:操作系统的内存管理,内核参数,进程,线程,栈空间等。

一台服务器可能会部署多个应用服务,需要根据服务器的硬件配置对操作系统内核参数进行调优,以发挥服务器最大性能。最基础的涉及到进程数、线程数、内存空间、线程栈空间等参数。

内存空间

操作系统把内存空间有有划分为 内核空间用户空间。内核空间分配和管理比较复杂,这里不细说。

32 位的 Windows 默认内存分配是系统占 2G,用户占 2G。

在 Linux 操作系统中,虚拟地址空间的部又分为 内核空间用户空间两部分,不同位数的系统,地址空间的范围也不同。例如,32位和64位的 Linux 系统内存空间分配如下:

linux-kernel-memory-allocation

  • 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,重启后会失效。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[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 线程栈空间大小:

1
2
3
4
5
6
7
8
9
[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)

设置线程栈空间大小:

1
2
3
4
# 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 值就是总的线程数,如下:

1
2
3
4
5
6
[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 可通过命令查看

    1
    2
    3
    4
    [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) ,网上还没找到该参数的源码解析,官方的英文描述极为抽象不好理解)

    该默认值可能偏小,或调整后的值仍不够使用时,创建线程会失败,会报: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 最大值 大于 应用服务的最大连接数。

查看修改限制参数

  1. 查看限制参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    # 查看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
  2. 修改限制参数

    永久修改,修改配置文件/etc/sysctl.conf,在文件尾添加参数。

    1
    2
    3
    4
    5
    6
    # 最大PID号值
    kernel.pid_max = 131075
    # 最大线程数
    kernel.threads-max = 13566
    #vm.max_map_count=map_count, 32G该值为262144, 256G该值为2097152
    vm.max_map_count = 262144

    执行生效命令

    1
    2
    3
    4
    [root@gxcentos ~]# sysctl -p
    kernel.pid_max = 131075
    kernel.threads-max = 13566
    vm.max_map_count = 262144

    查看修改结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    # 修改前的值
    [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

    临时修改,使用命令修改,重启后会失效

    1
    2
    3
    4
    5
    6
    7
    8
    [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

修改最大文件句柄数

  1. 查看操作系统允许打开的最大句柄数(文件描述符)

    1
    2
    3
    4
    5
    6
    7
    [root@localhost ~]# ulimit -n
    1024
    # open files
    [root@localhost ~]# ulimit -a
    ...省略...
    open files (-n) 1024
    .........
  2. 修改允许打开的文件句柄上限数。编辑 /etc/security/limits.conf文件,修改限制数。

    1
    2
    3
    4
    5
    # End of file
    root soft nofile 65535
    root hard nofile 65535
    * soft nofile 65535
    * hard nofile 65535

    退出当前登录终端,重新登录,不需要重启系统。

  3. 快速修改

    1
    2
    echo "* soft nofile 65536" >> /etc/security/limits.conf
    echo "* hard nofile 65536" >> /etc/security/limits.conf
  4. 修改运行中的进程的打开文件句柄限制数

    1
    2
    3
    4
    5
    6
    7
    # 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 的一半

  1. 查看每个用户的最大进程数限制值

    1
    2
    3
    4
    [root@gxcentos ~]# ulimit -u
    7143
    [root@gxcentos ~]# sysctl -a|grep threads-max
    kernel.threads-max = 14287
  2. 修改每个用户的最大进程数上限值。

    编辑 /etc/security/limits.conf文件,添加配置。

    1
    2
    3
    # End of file
    * soft nproc 8192
    * hard nproc 8192

    编辑 /etc/security/limits.d/20-nproc.conf (CentOS 7),修改里面的值,没有则添加

    1
    2
    3
    *          soft    nproc     8192
    * hard nproc 8192
    root soft nproc unlimited

    退出当前登录终端,重新登录,不需要重启系统。

    切换不同的用户,执行 ulimit -u命令查看用户的最大进程数上限是否已修改生效。

  3. 动态修改运行中服务的限制。

    Linux 系统为每个进程都在 /proc目录下创建一个以进程号(PID)为名的目录,目录中有 limits 文件(/proc/{pid}/limits),该文件加载的是当前进程的相关限制参数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    [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

    若是生产环境已启动不能随便重启,动态修改参数是不生效的。可执行下面命令使立即生效,如下。

    1
    2
    3
    4
    5
    6
    7
    # 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

相关资料

  1. Linux Kernel Docs
  2. Linux用户空间与内核空间(理解高端内存)
  3. 解决Unable to create new native thread
  4. JVM最大线程数
  5. Linux线程(进程)数限制分析

一台Java服务最多可以创建多少个线程

http://blog.gxitsky.com/2022/02/02/OperatingSystem-02-4-thread-number/

作者

光星

发布于

2022-02-02

更新于

2022-06-17

许可协议

评论