线上出现 StackOverflowError 或 Metaspace OOM,这两类问题你会分别怎么定位?
我会先控制影响,再按证据定位:StackOverflowError 通常来自无限递归、递归层数过深或单栈帧过大,应从重复调用栈找终止条件和调用环。Metaspace OOM 表示类元数据无法继续分配,常见原因是动态生成大量类、类加载器泄漏或上限过小。
我会先确认影响范围,同时控制故障继续放大。区分栈溢出、元空间耗尽的典型原因、证据和治理方式。StackOverflowError 通常来自无限递归、递归层数过深或单栈帧过大,应从重复调用栈找终止条件和调用环。Metaspace OOM 表示类元数据无法继续分配,常见原因是动态生成大量类、类加载器泄漏或上限过小。 线程过多更常表现为无法创建本地线程或进程内存耗尽,不应和单个线程的 StackOverflowError 混为一谈。
止损之后,我会按请求链路建立证据,而不是凭经验猜。沿着「方法递归压入栈帧 → 线程栈逼近上限 → 类持续加载元数据 → 本地内存接近预算 → 抛出 StackOverflow 或 Metaspace OOM」观察输入、状态与输出,这些阶段都可以从日志、指标或源码里验证。 方法递归压入栈帧 每次方法调用占用栈帧,局部变量、操作数和 JIT 情况影响实际深度。 线程栈逼近上限 无限递归或过深数据结构遍历会触发 StackOverflowError,增大 Xss 只能延后而不修复无穷递归。 -Xss / StackOverflowError 栈:递归深度证据。 jcmd VM.classloaderstats:Metaspace 按加载器定位。 我会先确认请求实际走到了哪条路径,再用运行数据验证,不会只看类名或配置猜测。
定位时我最关注这些参数、指标和容量关系。分别对深树递归和循环动态代理做边界测试;记录最大安全深度、加载类数和卸载后 Metaspace。
找到根因后先做最小修复,再用同样的流量验证。旧应用类加载器被日志线程的 ThreadLocal 和 JDBC Driver 注册表引用,导致整批类无法卸载。堆转储按 ClassLoader 统计并查看 GC Root 后,清理线程上下文和注销资源才解决。 只增大 Xss 掩盖无限递归:最大递归深度:无限递归改迭代栈。 动态代理类缓存按请求无限增长:线程数乘 Xss:动态类缓存设边界。 固定输入和基线 先在没有故障注入的环境执行上述配置,固定数据规模、并发度、运行时版本和预热时间。以「递归深度」为主基线,记录值应满足「小于演练上限 50%」;同时保存 最大递归深度、线程数乘 Xss,使后续变化能够回到同一时间轴比较。
恢复阶段要逐步放量,最后把监控和边界补齐。方案:更适合的场景:主要收益:代价与边界。 修复递归/改迭代:StackOverflow 来自算法深度:从根因消除栈增长:需要改写遍历状态。 调整 Xss:合法深调用且线程数可控:快速扩大单线程深度:减少可创建线程并增加本地内存。 限制 Metaspace + 治理加载器:动态类场景:故障更早且可定位:限制过小会误伤正常启动。 选型至少带上 堆与非堆容量、对象分配速率、存活率和停顿目标,并用上面的量化基线验证;