防止栈溢出需设可靠终止条件并优先迭代替代:覆盖所有输入路径(含负数、零、极大值),设深度阈值(普通≤500,结构明确≤80),禁用-Xss代替守卫,深度不确定时用Deque/Stack在堆中模拟调用栈。

设置正确的终止条件是防止栈溢出的第一道防线,但光有“if return”远远不够——它必须覆盖所有输入路径、抵御边界异常、并配合深度防护才真正可靠。
覆盖所有可能的输入路径
终止条件不是写一个判断就完事,而是要确保每条执行路径最终都能抵达出口。尤其注意被忽略的边缘情况:
- 数值类递归:检查负数、零、极大值。例如
if (n == 1)无法拦截n = -5,应改为if (n 或更严格的区间判断 - 引用类递归(如树、链表):每次调用子节点前,必须先判空。
if (node == null) return;要出现在访问node.left或node.right之前 - 集合类递归(如List、Map):用
isEmpty()而非size() == 0,避免空指针;对null输入也要单独判,不能假设参数非空
保证参数单调收敛且不越界
终止条件有效,前提是每次递归调用都在向出口靠近。如果参数不变、回退甚至发散,再严密的判断也形同虚设:
- 数值递归中,确保每次传入的
n-1、n/2等操作能让值稳定趋近基准值(如0或1) - 树遍历中,
process(node.left)和process(node.right)前必须确认node.left != null且node.right != null,否则会触发空对象上的无效递归 - 避免“伪递减”:比如用
Math.abs(n - 1)处理负数时,n = -1会变成0,再调用又变回1,陷入循环
叠加深度守卫作为第二道保险
即使终止逻辑正确,恶意输入或数据异常仍可能导致深度远超预期(比如用户上传嵌套1000层的JSON)。单靠出口不够,需主动设限:
立即学习“Java免费学习笔记(深入)”;
- 为递归方法增加
int depth参数,入口处校验:if (depth > 500) throw new IllegalArgumentException("Max recursion depth exceeded"); - 阈值按场景设定:普通业务建议≤500;已知结构(如B+树)不超过30层的,设80更稳妥
- 不要依赖
-Xss扩容代替守卫——栈空间终究有限,且增大后反而降低并发线程数
优先用迭代替代不可控递归
对深度不确定的场景(如解析外部配置、遍历用户自定义嵌套结构),终止条件再完善也不如根本不用递归:
- 用
Deque<Node>或Stack<Context>在堆内存中模拟调用栈,把“压栈”从线程栈转移到可伸缩的堆空间 - 每次循环处理一个节点,子任务不调用自身,而是
push进容器,完全规避self-call - 典型例子:二叉树中序遍历,递归版千层即爆,显式栈版本轻松处理万层以上


















