如何提升Java可创建线程数?TCP服务器高并发连接问题排查
我开发了一款TCP服务器,用于对接传感器等嵌入式设备(需保持长连接实现实时控制),采用TCP Socket+私有二进制协议。当前用Executors.newCachedThreadPool()为每个连接分配独立线程处理,当并发连接达到9462个时,JVM无法创建新线程,报错如下:
[1805.442s][warning][os,thread] Failed to start thread "Unknown thread" - pthread_create failed (EAGAIN) for attributes: stacksize: 1024k, guardsize: 0k, detached.
[1805.442s][warning][os,thread] Failed to start the native thread for java.lang.Thread "Thread-15915"
Exception in thread "Thread-0" java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached
at java.base/java.lang.Thread.start0(Native Method)
at java.base/java.lang.Thread.start(Thread.java:802)
at socketservertest.TCPServer.run(TCPServer.java:139)
at java.base/java.lang.Thread.run(Thread.java:833)
此时系统仍有40%可用内存,目标是支持至少5万并发连接,需要调整系统和JVM配置,同时优化架构。
一、Debian系统资源限制调整
系统层面的线程数、栈内存限制是当前直接瓶颈:
调整用户进程资源限制
- 编辑
/etc/security/limits.conf,添加或修改:
其中your_username soft nproc 65535 your_username hard nproc 65535 your_username soft stack 262144 your_username hard stack 262144nproc限制单个用户的最大进程/线程数,stack设置线程栈的软限制(单位:字节,262144即256k)。 - 若用systemd管理服务,编辑
/etc/systemd/system.conf,修改:
执行DefaultLimitNPROC=65535 DefaultLimitSTACK=262144systemctl daemon-reload生效。
- 编辑
调整内核参数
- 临时生效:
echo 100000 > /proc/sys/kernel/threads-max echo 200000 > /proc/sys/vm/max_map_count - 永久生效:编辑
/etc/sysctl.conf,添加:
执行kernel.threads_max = 100000 vm.max_map_count = 200000sysctl -p生效。
- 临时生效:
二、JVM参数优化
默认JVM线程栈大小为1M,大量线程会占用大量堆外内存,需调整:
- 减小线程栈大小:启动参数添加
-Xss256k(根据实际业务调整,最低可到128k,需测试避免栈溢出) - 合理分配堆内存:不要把堆内存占满,留足够空间给线程栈。例如系统总内存32G的话,堆内存设为
-Xmx16G,剩余内存可支撑5万线程(每个256k的话,50000*256k=12.5G,足够)。
三、架构优化(核心解决方案)
每个连接一个线程的模型根本无法支撑5万并发,即使调通系统参数,也会出现CPU上下文切换过载、内存耗尽的问题,必须改用IO多路复用模型:
替换线程池类型
立即停止使用Executors.newCachedThreadPool(),它会无限制创建线程。改用固定大小线程池,比如根据CPU核心数设置:this._threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);但这只是临时过渡,无法解决5万连接的根本问题。
切换到Java NIO/Selector模型
使用Java NIO的Selector实现多路复用,单个线程可以处理成百上千个连接的IO事件,避免为每个连接分配线程。核心思路是:- 用
ServerSocketChannel监听连接,注册到Selector - 每个客户端的
SocketChannel注册到Selector,监听读/写事件 - 用少量线程轮询
Selector,处理就绪的IO事件
- 用
使用Netty框架(推荐)
Netty封装了NIO细节,提供了成熟的TCP长连接解决方案,支持百万级并发,适合嵌入式设备的长连接场景。它通过Reactor模型实现多路复用,无需手动管理线程和IO事件,能大幅降低开发复杂度。
内容的提问来源于stack exchange,提问作者Dominick

