微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决冗余方法调用减少了可用的堆栈内存

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

解决方法

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。