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

xzv2uavs  于 2021-06-29  发布在  Java
关注(0)|答案(1)|浏览(337)

我在实际项目中遇到了StackOverflowerr,并制作了一个简单的模型来说明这个问题。它是一个测试类,它调用一些递归方法并保存错误的深度。

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() {
}

它在测试中获取51k或59k方法调用的深度。

public static void simpleMethod() {
    c += 0;
}

它在测试中获取48k或58k方法调用的深度。
为什么这些认识得到了不同的结果?我不明白在第二种情况下堆栈中有什么额外的数据。我认为simplemethod不应该影响堆栈内存,因为它不在调用链中。

kyxcudwk

kyxcudwk1#

由于性能原因,您遇到的问题可能与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/热点。见鬼,即使多次运行同一个应用程序也会导致 c 最后。当我试着用 simpleRecursion() 变体 c+=0; 而不是方法调用 simpleMethod(); ,堆栈大小保持不变,很可能是因为编译器足够聪明,可以使用相同的堆栈大小 2 .

相关问题