Java 字节码中的 Stack=4。 Java 编译器如何计算 4 值? (堆栈的深度)
Stack=4 in Java bytecode. How does the Java Compiler compute the 4 value? (depth of the the stack)
Java代码:
public class SimpleRecursion {
public int factorial(int n) {
if (n == 0) {
return 1;
}
return n * factorial(n - 1);
}
}
为阶乘方法给出了以下字节码(我执行了 javap 来生成它):
public int factorial(int);
descriptor: (I)I
flags: ACC_PUBLIC
Code:
stack=4, locals=2, args_size=2
0: iload_1
1: ifne 6
4: iconst_1
5: ireturn
6: iload_1
7: aload_0
8: iload_1
9: iconst_1
10: isub
11: invokevirtual #2 // Method factorial:(I)I
14: imul
15: ireturn
LineNumberTable:
line 4: 0
line 5: 4
line 7: 6
StackMapTable: number_of_entries = 1
frame_type = 6 /* same */
我了解到,在上面的块中的第五行中,stack=4表示堆栈最多可以有4个对象。
但是编译器是如何计算的?
由于栈的初始状态,以及每条指令对它的作用是众所周知的,你可以准确预测,随时会有什么样的项目进入操作数栈:
[ ] // initially empty
[ I ] 0: iload_1
[ ] 1: ifne 6
[ I ] 4: iconst_1
[ ] 5: ireturn
[ I ] 6: iload_1
[ I O ] 7: aload_0
[ I O I ] 8: iload_1
[ I O I I ] 9: iconst_1
[ I O I ] 10: isub
[ I I ] 11: invokevirtual #2 // Method factorial:(I)I
[ I ] 14: imul
[ ] 15: ireturn
JVM的验证器就是这么做的,在每条指令之后预测堆栈的内容,检查它是否适合作为后续指令的输入。但这有助于声明最大大小,因此验证者不需要维护动态增长的数据结构或为理论上可能的 64k 堆栈条目预分配内存。使用声明的最大大小,它可以在遇到将推送更多的指令时停止,因此它永远不需要比声明的更多的内存。
如您所见,声明的最大堆栈大小恰好达到一次,就在索引 9 处的 iconst_1
指令之后。
然而,这并不意味着编译器必须执行这样的指令分析。编译器具有从源代码派生的更高级别的代码模型,称为 Abstract syntax tree.
此结构将用于生成生成的字节码,它也可能已经能够预测该级别所需的堆栈大小。但是编译器实际如何执行它取决于实现。
Java代码:
public class SimpleRecursion {
public int factorial(int n) {
if (n == 0) {
return 1;
}
return n * factorial(n - 1);
}
}
为阶乘方法给出了以下字节码(我执行了 javap 来生成它):
public int factorial(int); descriptor: (I)I flags: ACC_PUBLIC Code: stack=4, locals=2, args_size=2 0: iload_1 1: ifne 6 4: iconst_1 5: ireturn 6: iload_1 7: aload_0 8: iload_1 9: iconst_1 10: isub 11: invokevirtual #2 // Method factorial:(I)I 14: imul 15: ireturn LineNumberTable: line 4: 0 line 5: 4 line 7: 6 StackMapTable: number_of_entries = 1 frame_type = 6 /* same */
我了解到,在上面的块中的第五行中,stack=4表示堆栈最多可以有4个对象。
但是编译器是如何计算的?
由于栈的初始状态,以及每条指令对它的作用是众所周知的,你可以准确预测,随时会有什么样的项目进入操作数栈:
[ ] // initially empty
[ I ] 0: iload_1
[ ] 1: ifne 6
[ I ] 4: iconst_1
[ ] 5: ireturn
[ I ] 6: iload_1
[ I O ] 7: aload_0
[ I O I ] 8: iload_1
[ I O I I ] 9: iconst_1
[ I O I ] 10: isub
[ I I ] 11: invokevirtual #2 // Method factorial:(I)I
[ I ] 14: imul
[ ] 15: ireturn
JVM的验证器就是这么做的,在每条指令之后预测堆栈的内容,检查它是否适合作为后续指令的输入。但这有助于声明最大大小,因此验证者不需要维护动态增长的数据结构或为理论上可能的 64k 堆栈条目预分配内存。使用声明的最大大小,它可以在遇到将推送更多的指令时停止,因此它永远不需要比声明的更多的内存。
如您所见,声明的最大堆栈大小恰好达到一次,就在索引 9 处的 iconst_1
指令之后。
然而,这并不意味着编译器必须执行这样的指令分析。编译器具有从源代码派生的更高级别的代码模型,称为 Abstract syntax tree.
此结构将用于生成生成的字节码,它也可能已经能够预测该级别所需的堆栈大小。但是编译器实际如何执行它取决于实现。