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