【问题标题】:Redundant method call reduces available stack memory冗余方法调用减少了可用堆栈内存
【发布时间】:2020-12-26 22:07:23
【问题描述】:

我在实际项目中遇到了 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 不应该影响堆栈内存,因为它不在调用链中。

【问题讨论】:

  • 停止递归的条件在哪里?
  • @HoussemBadri 抛出堆栈溢出错误时递归停止。
  • for 循环的意义何在?只是打电话给simpleRecursion(); 不足以证明这一点吗?
  • @khelwood 是不希望的行为还是实际行为(抛出异常以停止递归的事实)?
  • @HoussemBadri 他们故意造成堆栈溢出以查看堆栈的深度。

标签: java stack-memory


【解决方案1】:

由于性能原因,您遇到的问题可能与 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 的堆栈大小。

我的猜测是,当方法 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.

【讨论】:

  • 很棒的解释。谢谢你。如果我使 simpleMethod 变得足够困难,那么 simpleRecursion 的堆栈大小有可能会减少。是真的吗?
猜你喜欢
  • 2019-06-20
  • 2015-11-18
  • 2014-08-28
  • 1970-01-01
  • 1970-01-01
  • 2017-12-01
  • 2018-05-02
  • 2012-01-20
  • 2011-11-01
相关资源
最近更新 更多