http长连接本身不直接导致内存溢出,但管理不当会持续占用线程、socket、buffer等资源,引发线程耗尽或堆内存堆积,最终出现outofmemoryerror;需通过jstack/jstat/netstat识别假性饱和,配置connectiontimeout、maxkeepaliverequests等参数约束生命周期,并修复外部调用超时、session滥用、流未关闭等应用层阻塞点。

HTTP长连接本身不直接导致内存溢出,但它会持续占用线程和相关资源(如Socket、Buffer、Session对象),若管理不当,极易引发线程耗尽或堆内存堆积,最终表现为 OutOfMemoryError: unable to create new native thread 或 Java heap space。这类问题在高并发、慢响应或客户端异常断连场景下尤为典型。
识别长连接是否正在霸占线程
关键看线程池是否“假性饱和”——线程数接近 maxThreads,但CPU不高、请求响应缓慢、大量线程处于 WAITING 或 TIMED_WAITING 状态(如等待数据库、远程接口、文件IO)。
- 用
jstack -l <pid></pid>导出线程快照,搜索http-nio-开头的线程,观察其堆栈是否卡在SocketInputStream.read、await、poll或业务层阻塞调用上 - 检查
jstat -gc <pid></pid>输出:若FGC频繁但堆使用率不高,可能是线程栈+本地变量持续增长挤压堆空间 - 对比
netstat -an | grep :8080 | grep ESTABLISHED | wc -l和 Tomcat 当前活跃线程数,若前者远大于后者,说明存在大量空闲长连接未释放
调整连接器参数控制长连接行为
Tomcat 默认启用 HTTP/1.1 持久连接,需通过 server.xml 中的 <connector></connector> 显式约束生命周期。
-
设置连接超时:添加
connectionTimeout="20000"(单位毫秒),防止空闲连接无限挂起 -
限制长连接最大请求数:配置
maxKeepAliveRequests="100",达到后主动关闭连接,避免单连接反复复用导致线程长期绑定 -
禁用不必要的 Keep-Alive:对纯API服务或移动端调用,可加
keepAliveTimeout="5000"缩短保活窗口;极端情况设为0彻底禁用(HTTP/1.0 行为) -
区分协议模式:确保使用
protocol="org.apache.coyote.http11.Http11NioProtocol"(NIO)而非 BIO,避免一个连接独占一个线程
排查并修复应用层阻塞点
长连接“霸占线程”的本质,往往是业务代码让线程卡在同步等待中,无法及时释放。
- 检查是否有未设超时的外部调用:如
HttpClient、RestTemplate、JDBC 查询,必须显式配置connectTimeout和readTimeout - 避免在请求处理链中使用
Thread.sleep()、wait()或无界队列BlockingQueue.put() - 确认 Session 是否被滥用:过长的
maxInactiveInterval(如设为 1 小时)会让大量HttpSession实例滞留内存,尤其当 session 中存了大对象或未序列化资源 - 审查过滤器(Filter)和拦截器:是否存在日志全量打印请求体、同步写磁盘、未关闭流(
InputStream/OutputStream)等隐患
配合 JVM 与系统级防护
仅靠容器配置不够,需从运行时环境筑起防线:
- 在
JAVA_OPTS中加入-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dump,捕获线程爆炸时的堆现场 - 限制单线程栈大小:
-Xss256k(默认通常 1MB),降低unable to create new native thread触发阈值,倒逼优化线程使用 - Linux 系统层面检查
ulimit -u(用户进程数上限)和cat /proc/sys/kernel/threads-max,避免 OS 层先于 JVM 报错 - 启用 JMX 监控
ThreadPoolMBean,实时跟踪currentThreadCount、currentThreadsBusy、keepAliveCount
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











