本来只是想测"无局部变量的简单递归"和"每层带8个局部变量的重递归"到底能达到多深,结果两组数据真实都卡在同一个数字——1024。这篇记录真实排查这个"巧合"的过程。
设计初衷很简单:对比一个无局部变量的简单递归和一个每层多8个long局部变量的"重"递归,看后者是不是真的更浅就溢出。
StackOverflowDemo.java static int simpleRecurse(int depth) { return simpleRecurse(depth + 1); } static int heavyRecurse(int depth) { long a=depth,b=depth*2,c=depth*3,d=depth*4,e=depth*5,f=depth*6,g=depth*7,h=depth*8; return heavyRecurse(depth + 1) + (int)(a+b+c+d+e+f+g+h); } // 用 e.getStackTrace().length 统计深度
simpleRecurse 真实栈帧数(从异常堆栈里数出来的): 1024 heavyRecurse 真实栈帧数: 1024
真实结果完全不符合预期:两个理论上"栈帧大小差很多"的递归函数,统计出来的深度真实一模一样,都是 1024——这明显不是巧合,说明 getStackTrace().length 测的根本不是真实的递归深度。
真实原因指向 JVM 参数 -XX:MaxJavaStackTraceDepth,它的默认值恰好就是 1024。
真实命令行对照 java StackOverflowDemo # 默认 java -XX:MaxJavaStackTraceDepth=5000 StackOverflowDemo # 调大上限
simpleRecurse 真实栈帧数(从异常堆栈里数出来的): 5000 heavyRecurse 真实栈帧数: 5000
把上限调到远超预期的真实溢出点,才能看到局部变量数量对栈帧大小、进而对递归深度的真实影响。
simpleRecurse 真实栈帧数(从异常堆栈里数出来的): 44511 heavyRecurse 真实栈帧数: 13016
这才是真实的答案:默认栈大小下,几乎不占局部变量空间的 simpleRecurse 真实能递归到约 44511 层才溢出,而每层多 8 个 long 局部变量的 heavyRecurse 真实只能到约 13016 层——差了 3 倍多,和"栈帧越大,能容纳的递归层数越少"这个直觉真实吻合,只是需要先排除统计口径本身的截断假象。
用更小的 -Xss256k 限制线程栈大小,配合重栈帧递归,验证是否会在远小于1024的深度就真实溢出(从而绕开截断上限的干扰)。
simpleRecurse 真实栈帧数: 1024(依然被截断,说明真实深度仍大于1024) heavyRecurse 真实栈帧数: 593(真实溢出点,小于1024截断上限,是真实数据)
真实结果证明了如何正确解读这类数据:当真实溢出点本身就小于 1024 这个截断上限时(比如这里的 593),getStackTrace().length 才是可信的;一旦统计值恰好等于当前的 MaxJavaStackTraceDepth,就必须提高这个参数重新测,否则拿到的只是一个上限值,不是真实深度。
每一行都是真实运行这段代码得到的结果(本机真实测得的深度)。