jetty 在高并发 websocket 场景下内存占用远低于 tomcat,因其对象复用机制和 gc 友好的生命周期管理;切换需排除 tomcat、引入 jetty,并配置线程池与日志级别。

Tomcat 在高并发 WebSocket 场景下容易出现内存暴涨甚至 OOM,Jetty 同样连接数下内存占用通常不到 Tomcat 的 1/10 —— 这不是配置问题,是底层对象模型和连接生命周期管理的根本差异。
WebSocket 长连接压测时 Tomcat 内存持续上涨
当 WebSocket 连接数超过 2000,Tomcat(默认 NIO 模式)的老年代会快速填满,WsFrameServer、WsSessionImpl 和关联的 ByteBuffer 实例大量堆积在堆中无法回收。即使连接已断开,GC 也很难及时清理这些持有引用的对象。
- 典型现象:
java.lang.OutOfMemoryError: Java heap space出现在连接数达 4000 左右,JVM 堆使用率 >95% - 根本原因:Tomcat 的 WebSocket 实现为每个连接分配独立的
WsSession和配套事件处理器,且部分状态绑定到容器级线程池或静态缓存中 - 影响范围:Spring Boot 2.3–3.3 默认 Tomcat 版本(9.0.x / 10.1.x)均存在该倾向,尤其在频繁建连/断连场景下更明显
Jetty 的 WebSocket 对象复用机制更轻量
Jetty 使用基于 EndPoint + Session 的扁平化模型,连接生命周期与 GC 友好对齐;其 WebSocketSession 不依赖容器级线程上下文,且多数缓冲区采用池化 ByteBuffer 或直接复用 Netty-style 的 DirectBuffer。
- 实测对比(5000 连接,8 核 32G 服务器):
top -p <pid></pid>显示 Jetty 进程 RSS 约 1.1G,Tomcat 达 25G+ - 关键配置差异:Jetty 默认启用
WebSocketServletFactory.setBufferSize(8192)并自动复用 buffer,而 Tomcat 的maxHttpHeaderSize和websocket.bufferSize参数对内存影响有限 - 注意点:Jetty 9.4+ 要求显式调用
session.close()才能触发资源释放,不主动 close 仍会泄漏 —— 但泄漏速度远低于 Tomcat
切换 Jetty 后 WebSocket 性能没变快,但稳定性显著提升
吞吐量(QPS)和平均延迟在简单 echo 场景下三者差距不大,真正拉开差距的是连接维持能力和故障恢复能力。Jetty 在连接抖动、客户端异常断连后更少出现“假连接”或 session 卡死。
- 切换方式:在
pom.xml中排除spring-boot-starter-tomcat,引入spring-boot-starter-jetty - 必须补的配置:在
application.yml中设server.jetty.threads.max: 200(默认仅 50),否则高并发下线程池成为瓶颈 - 别忽略日志:Jetty 的
org.eclipse.jetty.websocket日志级别建议设为WARN,避免DEBUG下海量onOpen/onClose日志拖慢吞吐
Undertow 其实更适合 WebSocket,但 Jetty 是最平滑的替代方案
如果你已经在线上用 Tomcat,临时切 Undertow 风险较高:它不支持 JSP、部分 Servlet 容器 API 兼容性较弱,且 Spring Boot 对其 WebSocket 的自动配置不如 Jetty 成熟。Jetty 提供了几乎零代码修改的迁移路径,同时规避了 Tomcat 最致命的内存缺陷。
真正难处理的,是那些既依赖 Tomcat 特有行为(比如某些 Filter 生命周期钩子)、又需要撑住上万 WebSocket 连接的旧系统 —— 这类场景下,光换容器不够,得重审 session 存储策略和心跳保活逻辑。











