冗余方法调用减少了可用的堆栈内存

Redundant method call reduces available stack memory

我在实际项目中遇到了 WhosebugError,并制作了一个简单的模型来显示问题。 调用一些递归方法并保存错误深度的测试class。

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 (WhosebugError e) {
                sum += c;
            }
        }
        System.out.println("Average method call depth: " + (sum / exps));
    }

    public static void simpleRecursion() {
        simpleMethod();
        ++c;
        simpleRecursion();
    }
}

simpleMethod 有两个版本:

public static void simpleMethod() {
}
  1. 它在测试中获得 51K 或 59K 方法调用的深度。
public static void simpleMethod() {
    c += 0;
}
  1. 它在测试中获得 48K 或 58K 方法调用的深度。

为什么这些实现得到了不同的结果?在第二种情况下,我无法理解堆栈中有哪些额外数据。在我看来,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

没有 c+=0; 行的 simpleMethod() 方法:

  public static void simpleMethod();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=0, locals=0, args_size=0
         0: return
      LineNumberTable:
        line 25: 0

带有 c+=0; 行的 simpleMethod(); 方法:

  public static void simpleMethod();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=0, 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.[=35= 的堆栈大小]

我猜当方法 simpleMethod() 被 JVM/JIT/HotSpot 内联到 simpleRecursion() 时(参见其他问题,例如 Are there inline functions in java? or Would Java inline method(s) during optimization?),它会增加堆栈大小simpleRecursion() 为所需的额外堆栈大小 simpleMethod() 腾出空间。现在 simpleRecursion() 的堆栈大小更大,这导致更早达到 WhosebugError 的限制。

不幸的是,由于涉及 JIT/HotSpot,我无法验证这一点。哎呀,即使 运行 相同的应用程序多次在最后导致 c 的不同值。当我尝试使用 simpleRecursion() 变体,其中使用 c+=0; 而不是对 simpleMethod(); 的方法调用时,堆栈大小保持不变,很可能是因为编译器足够聪明使用相同的堆栈大小 2.