You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Tomcat长连接安全处理及Spring Boot OOM调优咨询

问题根因

这类OOM本质不是传统意义上的内存泄漏,是资源边界缺失导致的内存耗尽:默认配置的RestTemplate没有任何超时限制、没有连接数上限,下游响应慢到数分钟时,每个挂起的请求会持续占住Tomcat工作线程、持有请求/响应缓冲对象、HTTP连接资源,入口请求不断堆积,所有关联对象都无法被GC回收,最终堆内存被撑满。

应用层修复(优先级最高,改完即可解决90%以上的同类问题)
  • 给RestTemplate配置严格的超时和连接池限制,绝对不能用默认的无限超时实现。推荐替换成Apache HttpClient实现的请求工厂,配置示例如下:
@Bean
public RestTemplate restTemplate() {
    PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
    // 全局最大连接数,结合单实例压测值设置,常规场景设200足够
    connManager.setMaxTotal(200);
    // 单下游路由的最大并发连接,针对故障下游单独设置,避免占满全部连接
    connManager.setDefaultMaxPerRoute(50);

    RequestConfig requestConfig = RequestConfig.custom()
            // 建立TCP连接超时,设3s即可,连不上直接抛错
            .setConnectTimeout(3000)
            // 从连接池获取连接超时,设1s,池满直接报错不排队
            .setConnectionRequestTimeout(1000)
            // 响应读取超时,按业务可接受最大等待时间设置,最长不超过30s,绝对不允许等数分钟
            .setSocketTimeout(10000)
            .build();

    CloseableHttpClient httpClient = HttpClients.custom()
            .setConnectionManager(connManager)
            // 自动回收30s以上的空闲连接,避免僵死连接占资源
            .evictIdleConnections(30, TimeUnit.SECONDS)
            .setDefaultRequestConfig(requestConfig)
            .build();

    return new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient));
}

注意几个核心参数不能妥协:读超时必须卡死上限,连接池大小必须结合下游承载能力设置,连接获取超时必须配置,避免请求在队列里无限堆积。

  • 调整RestTemplate响应缓冲逻辑:默认实现会把整个响应体全读到内存再做序列化,如果接口返回体较大,慢响应场景下缓冲内存占用会非常高,大响应体场景可以替换为支持流式处理的请求工厂,避免全量数据缓冲到堆内存。
  • 给故障下游加舱壁隔离和熔断。针对慢响应的下游接口单独做并发限制:比如最多只允许30个并发请求调用该接口,超过阈值直接快速失败返回降级结果,不占用Tomcat工作线程。同时配置熔断规则:如果该下游慢调用(比如响应超过5s)占比超过50%,直接熔断一段时间,期间所有请求走降级逻辑,等下游恢复后再通过半开探测逐步恢复调用,避免雪崩。
  • 收紧Tomcat配置,不要让请求无限排队。默认Tomcat的acceptCount(请求等待队列长度)是100,当工作线程全被慢请求占满时,队列里堆积的上百个请求每个都会持有Request/Response对象占用内存,很容易触发OOM。配置示例:
server:
  tomcat:
    threads:
      # 最大工作线程结合CPU核数设置,8核实例设200足够,不要盲目调大
      max: 200
      # 最小空闲线程留10个应对突发流量即可
      min-spare: 10
    # 请求等待队列长度设为20,比默认值小很多,队列满了直接返回503拒绝请求
    accept-count: 20
    # Tomcat侧连接超时设2s,避免僵死连接占资源
    connection-timeout: 2000ms

核心逻辑是:队列宁可短一点,快速拒绝多余请求,也不要无限堆积把整个进程拖死。

JVM层兜底配置
  • 确保JVM正确识别k8s容器的内存限制:Java 8u191+、Java11+默认支持容器内存感知,低版本Java8需要加启动参数-XX:+UseCGroupMemoryLimitForHeap;堆内存设置为容器内存limit的70%即可,剩余30%留给元空间、直接内存、JVM自身开销,避免堆占满容器内存触发系统OOMKill。
  • 配置OOM自动堆转储,方便事后排查问题:加启动参数-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/heapdump.hprof,对应路径提前挂载存储,避免dump文件打满容器根盘。
  • 开启GC日志留存,方便观测内存上涨趋势:Java11+用参数-Xlog:gc*,gc+age=trace,safepoint:file=/var/log/gc.log:utctime,pid,tags:filecount=5,filesize=100m,Java8调整为对应版本的GC日志参数即可。
  • 合理限制直接内存大小:加参数-XX:MaxDirectMemorySize=256m(按实际业务场景调整,一般不超过堆内存的1/4),避免NIO相关的直接内存占用过高耗尽容器内存。
K8s运维层防护
  • 配置合理的就绪探针:如果服务出现线程池满、大量请求超时的情况,就绪探针探测失败会自动将实例从Service端点摘除,不再转发新流量进来,避免故障实例被持续压垮。
  • 给Pod设置Guaranteed级别的QoS:即CPU和内存的requests值与limits值完全相等,避免节点资源不足时故障实例被优先驱逐。
  • 流量入口侧加并发限流:在Ingress或服务网格层给服务设置单实例最大并发阈值,超过阈值直接返回429响应,避免突发流量把实例打垮。

排查这类问题不要上来就定位内存泄漏,优先拿堆dump看占用内存最多的对象,90%以上的场景都是挂起的HTTP请求关联的byte数组、响应对象,根源就是没做超时和并发限制,把资源边界卡死问题自然解决。

内容的提问来源于stack exchange,提问作者darth jemico

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 09:57:17