应停用Executors.newCachedThreadPool(),改用显式配置的ThreadPoolExecutor并绑定容器资源边界;因其使用SynchronousQueue和Integer.MAX_VALUE导致线程无限创建、绕过cgroup内存限制,易触发OOMKilled。

直接停用 Executors.newCachedThreadPool(),改用显式配置的 ThreadPoolExecutor,并绑定容器资源边界——这是最有效、最根本的解法。
为什么 CachedThreadPool 在容器里特别危险
它内部使用 SynchronousQueue + maximumPoolSize = Integer.MAX_VALUE,意味着:
- 任务一来就新建线程,不排队、不等待;
- 容器内存限制(如 --memory=2g)对它完全透明;
- 每个线程默认占 1MB 栈空间(-Xss),1000 个线程就是 1GB 堆外内存;
- JVM 还没触发 GC,cgroup 已经把进程 OOMKilled 了。
替换方案:按容器 CPU 和内存上限重建线程池
不要复用全局池,每个业务模块独立声明,参数必须动态适配容器环境:
在 Linux 上通过 Docker 运行 OpenClaw,并使用 Tailscale 实现远程访问。⚠️ 涉及 sudo、Docker、Tailscale和凭证挂载——请先查阅安全章节...
- 核心线程数
corePoolSize=Runtime.getRuntime().availableProcessors()(它会读取 cgroup CPU quota,不是宿主机核数) - 最大线程数
maximumPoolSize≤corePoolSize × 4(IO 密集型上限,CPU 密集型建议 ≤corePoolSize + 1) - 任务队列用
ArrayBlockingQueue(512)(有界!避免无限堆积) - 拒绝策略设为
CallerRunsPolicy,让过载压力回传到调用方,防止雪崩 - 线程工厂必须命名,例如
new NamedThreadFactory("notify-pool"),便于jstack定位
配套必须做的 JVM 容器感知加固
光改线程池不够,JVM 自身也得“认得清自己在哪”:
- 加
-XX:+UseContainerSupport(JDK 8u191+ 默认开启,老版本必须显式加) - 堆内存改用比例制:
-XX:MaxRAMPercentage=75.0 -XX:InitialRAMPercentage=70.0,别写死-Xmx - 限定元空间:
-XX:MaxMetaspaceSize=256m - 限制堆外内存:
-XX:MaxDirectMemorySize=256m - GC 线程数对齐容器 CPU 限额:
-XX:ParallelGCThreads=2 -XX:ConcGCThreads=2(若docker run --cpus=2)
上线前验证三件事
避免“改完以为好了,压测才爆雷”:
- 进容器执行
cat /sys/fs/cgroup/memory.max和cat /sys/fs/cgroup/cpu.max,确认 JVM 看到的是容器值,不是宿主机值 - 启动后查
jstat -gc <pid>,验证初始堆大小是否接近容器内存 × 70% - 用
ps -eLf | grep java | wc -l观察线程总数,高并发下应稳定在maximumPoolSize附近,不持续飙升

















