问题描述
我在我的真实项目中遇到了 StackOverflowError 并制作了显示问题的简单模型。 它是调用一些递归方法并保存错误深度的测试类。
public class Main {
static int c = 0;
public static void main(String[] args) {
long sum = 0;
int exps = 100;
for (int i = 0; i < exps; ++i) {
c = 0;
try {
simpleRecursion();
} catch (StackOverflowError e) {
sum += c;
}
}
System.out.println("Average method call depth: " + (sum / exps));
}
public static void simpleRecursion() {
simpleMethod();
++c;
simpleRecursion();
}
}
simpleMethod 有两个版本:
public static void simpleMethod() {
}
public static void simpleMethod() {
c += 0;
}
为什么这些实现会得到不同的结果?在第二种情况下,我无法理解堆栈中存在哪些额外数据。我认为 simpleMethod 不应该影响堆栈内存,因为它不在调用链中。
解决方法
由于性能原因,您遇到的问题可能与 JVM 的内联方法有关。内联方法可能会影响为该方法分配的堆栈大小。您可以使用 javap -v
检查某个方法的堆栈大小有多大,该方法在调用该方法时被分配。对于您的代码,javap -v
的结果如下:
simpleRecursion()
方法:
public static void simpleRecursion();
descriptor: ()V
flags: ACC_PUBLIC,ACC_STATIC
Code:
stack=2,locals=0,args_size=0
0: invokestatic #13 // Method simpleMethod:()V
3: getstatic #2 // Field c:I
6: iconst_1
7: iadd
8: putstatic #2 // Field c:I
11: invokestatic #3 // Method simpleRecursion:()V
14: return
LineNumberTable:
line 19: 0
line 20: 3
line 21: 11
line 22: 14
没有 simpleMethod()
行的 c+=0;
方法:
public static void simpleMethod();
descriptor: ()V
flags: ACC_PUBLIC,ACC_STATIC
Code:
stack=0,args_size=0
0: return
LineNumberTable:
line 25: 0
带有 simpleMethod();
行的 c+=0;
方法:
public static void simpleMethod();
descriptor: ()V
flags: ACC_PUBLIC,args_size=0
0: getstatic #2 // Field c:I
3: iconst_0
4: iadd
5: putstatic #2 // Field c:I
8: return
LineNumberTable:
line 25: 0
line 26: 8
具有空体的方法变体需要 0
的堆栈大小,其中带有 c+=0;
行的方法变体需要 2
的堆栈大小。
我猜当方法 simpleMethod()
被 JVM/JIT/HotSpot 内联到 simpleRecursion()
时(参见其他问题,如 Are there inline functions in java? 或 Would Java inline method(s) during optimization?)它会增加simpleRecursion()
的堆栈大小,以便为所需的额外堆栈大小 simpleMethod()
腾出空间。现在 simpleRecursion()
的堆栈大小更大,这导致 StackOverflowError
更早达到限制。
不幸的是,由于涉及 JIT/HotSpot,我无法验证这一点。哎呀,即使多次运行同一个应用程序,最后也会导致 c
的值不同。当我尝试使用 simpleRecursion()
变体(其中使用 c+=0;
而不是对 simpleMethod();
的方法调用)时,堆栈大小保持不变,很可能是因为编译器足够聪明使用相同的 2
堆栈大小。