线程数 × -Xss 即为平台线程虚拟内存硬开销,每个线程独占对应大小的虚拟地址空间;虚拟线程栈则按需分配、均摊约1KB,不遵循该公式。

线程数 × -Xss 就是虚拟内存硬开销
OpenJDK 每个线程(平台线程)启动时,JVM 会向操作系统申请一块连续的虚拟地址空间,大小由 -Xss 决定。这个空间在 Linux 上哪怕线程只用几 KB,也会占用完整配额——不是“用了多少算多少”,而是“申请多少占多少”。1000 个线程 × -Xss1m = 1GB 虚拟内存仅用于栈,不包含堆、元空间、直接内存等。
常见误判是:“我堆只设了 2G,系统总内存 4G,应该够”。但容器或物理机实际崩溃前,往往先报 unable to create new native thread 或被 OOMKilled,根源就是虚拟内存(ulimit -v)或 mmap 区域(/proc/sys/vm/max_map_count)耗尽,而非堆内存不足。
- Linux x86_64 默认
max_map_count通常为 65530,每个线程至少占 1 个 vma 区域;超限后new Thread().start()直接失败 - Docker 容器默认继承宿主机
max_map_count,但内存限制(-m)只约束 RSS,不管虚拟内存,极易隐性超限 -
jstack <pid></pid>抽样看活跃线程平均栈帧数,若普遍 ≤ 15 层,说明-Xss256k很可能安全
怎么定 -Xss 值:看调用链深度,不是看代码行数
栈溢出(StackOverflowError)本质是当前线程的调用链太深,超出栈容量能容纳的帧数。但“深”不等于“代码多”——一个 Spring AOP + 多层代理 + 反射调用的方法,可能 8 层就压满 256k;而纯业务逻辑线性调用,30 层也未必溢出。
实操建议:
- 压测时加 JVM 参数
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps,同时监控日志中是否出现java.lang.StackOverflowError—— 这是最直接信号 - 用
jstack <pid> | grep 'java.lang.Thread' -A 20</pid>抽 5–10 个活跃工作线程,数其栈帧数量(每行 “at …” 算一帧),取 P95 值作为基准 - 若 P95 栈帧数 ≤ 100,
-Xss256k通常足够;若 ≥ 200,谨慎下探到-Xss384k或保留-Xss512k - 避免在 IO 密集型服务(如 Netty、WebFlux)里盲目设
-Xss128k:某些 TLS 握手、JSON 序列化深度递归路径会意外吃掉大量栈
虚拟线程让“按线程数规划栈”变成伪命题
Java 21+ 启用虚拟线程后,-Xss 对虚拟线程完全无效。它的栈不走 native 内存,而是以对象形式存在堆上,初始仅几百字节,按需增长。10 万个 Thread.startVirtualThread(() -> Thread.sleep(60_000)) 实测栈总开销约 100MB,即均摊 ≈ 1KB/线程。
但这不意味着可以无脑堆虚拟线程:
- 载体线程(carrier thread)仍是平台线程,仍受
-Xss约束;高并发下载体线程池若配置不当,仍会触发unable to create new native thread - 虚拟线程阻塞时,栈数据从载体线程复制到堆,若局部变量引用大对象或未及时释放,会推高 GC 压力
- 调试时
jstack看不到虚拟线程真实栈,得用jcmd <pid> VM.native_memory summary</pid>或 JFR 事件jdk.VirtualThreadParked观察
真正要盯住的三个数字
别只盯着 -Xss,这三个值共同决定线程栈能否稳住:
-
ulimit -s:OS 级单线程栈软限制(单位 KB),JVM-Xss不能超过它,否则启动报错Invalid initial thread stack size -
/proc/<pid>/maps</pid>中stack段数量:等于当前存活平台线程数,每段长度应 ≈-Xss值,可验证是否真被应用生效 -
jstat -gc <pid></pid>的NGCMN/NGCMX:如果新生代频繁 GC 且EC(Eden)持续偏低,可能是虚拟线程栈对象导致 Minor GC 加速——此时要查是否误用大数组或长生命周期局部变量
栈内存不是孤立参数,它卡在 OS 限制、JVM 行为、业务调用特征三者交界处。调得过小,线上随机 StackOverflowError;调得过大,还没撑到堆 OOM,就被内核拒掉新线程。最稳的节奏是:先用 jstack 看真实深度,再阶梯下调 -Xss,最后用 cat /proc/<pid>/maps | grep stack | wc -l</pid> 对齐线程数验证。


















