首页 > 解决方案 > 冗余方法调用减少了可用堆栈内存

问题描述

我在实际项目中遇到了 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() {
}
  1. 它在测试中获得 51K 或 59K 方法调用的深度。
public static void simpleMethod() {
    c += 0;
}
  1. 它在测试中获得 48K 或 58K 方法调用的深度。

为什么这些认识会得到不同的结果?在第二种情况下,我无法理解堆栈中有哪些额外数据。我认为 simpleMethod 不应该影响堆栈内存,因为它不在调用链中。

标签: javastack-memory

解决方案


由于性能原因,您遇到的问题可能与 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, locals=0, args_size=0
         0: return
      LineNumberTable:
        line 25: 0

simpleMethod();带线的方法c+=0;

  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

我的猜测是,当该方法simpleMethod()simpleRecursion()JVM/JIT/HotSpot 内联时(请参阅其他问题,如Java 中有内联函数吗?优化期间 Java 内联方法吗?simpleRecursion() )它会增加堆栈大小所需的额外堆栈大小的空间simpleMethod()。现在 的堆栈大小simpleRecursion()更大,这导致StackOverflowError更早达到极限。

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


推荐阅读