字节码初览
示例程序
/**
* @author shaoming
*/
public class ByteCodeView {
public static void main(String[] args) {
System.out.println(factorial(5));
}
public static int factorial(int n) {
if (n == 0) {
return 1;
}
return n * factorial(n - 1);
}
}
准备命令
示例只展示最基础的准备指
- 编译指令
javac ByteCodeView.java
- 输出输出字节码的附加信息
javap -verbose ByteCodeView > ByteCode.txt
字节码详解
字节码助记符请参考网络上的助记符表
Classfile /Users/shaoming/workspace/self/extension/src/cn/shellming/t0/ByteCodeView.class
Last modified 2021-8-2; size 528 bytes
MD5 checksum 825e7254ffc19ed5e1ec428c3604dac7
Compiled from "ByteCodeView.java"
public class cn.shellming.t0.ByteCodeView // 类在工程内的类路径
minor version: 0 // JDK 小版本号
major version: 52 // JDK 大版本号
flags: ACC_PUBLIC, ACC_SUPER // public限定类,当调用invokespecial时,需要特殊处理父类的方法
Constant pool: // 常量池
#1 = Methodref #6.#18 // java/lang/Object."<init>":()V
#2 = Fieldref #19.#20 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Methodref #5.#21 // cn/shellming/t0/ByteCodeView.factorial:(I)I
#4 = Methodref #22.#23 // java/io/PrintStream.println:(I)V
#5 = Class #24 // cn/shellming/t0/ByteCodeView
#6 = Class #25 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 factorial
#14 = Utf8 (I)I
#15 = Utf8 StackMapTable
#16 = Utf8 SourceFile
#17 = Utf8 ByteCodeView.java
#18 = NameAndType #7:#8 // "<init>":()V
#19 = Class #26 // java/lang/System
#20 = NameAndType #27:#28 // out:Ljava/io/PrintStream;
#21 = NameAndType #13:#14 // factorial:(I)I
#22 = Class #29 // java/io/PrintStream
#23 = NameAndType #30:#31 // println:(I)V
#24 = Utf8 cn/shellming/t0/ByteCodeView
#25 = Utf8 java/lang/Object
#26 = Utf8 java/lang/System
#27 = Utf8 out
#28 = Utf8 Ljava/io/PrintStream;
#29 = Utf8 java/io/PrintStream
#30 = Utf8 println
#31 = Utf8 (I)V
{
public cn.shellming.t0.ByteCodeView();
descriptor: ()V
flags: ACC_PUBLIC // public 方法
Code: // 方法栈的大小为1,局部变量表的大小为1,入参数量为1 默认无参的构造方法怎么会有入参呢?其实Java语言有一个潜规则:在任何实例方法里面都可以通过this来访问到此方法所属的对象。Java只是把这种机制后推到编译阶段完成而已。所以,这里的1都是指this这个参数而已。
stack=1, locals=1, args_size=1
0: aload_0 // 从局部变量0中装载引用类型值入栈
1: invokespecial #1 // Method java/lang/Object."<init>":()V , 调用常量池中的 "#1"初始化父类的构造方法
4: return // 返回销毁方法栈
LineNumberTable:
line 6: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC // 一个public static 的方法
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 通过getstatic获取系统的输出流静态方法
3: iconst_5 // 把int常量5入栈
4: invokestatic #3 // 调用静态方法 factorial:(I)I
7: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
10: return
LineNumberTable:
line 9: 0
line 10: 10
public static int factorial(int);
descriptor: (I)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=1, args_size=1
0: iload_0 // 从局部变量0中装载引用类型值入栈 就是读入参5 [5]
1: ifne 6 // 若栈顶int类型值不为0则跳转到6 [5]
4: iconst_1 // 定义一个常量1并压入栈中 [1]
5: ireturn // 返回栈顶数据
6: iload_0 // [5]
7: iload_0 // [5, 5]
8: iconst_1 // [1, 5, 5]
9: isub // [4, 5]
10: invokestatic #3 // 用栈顶数据调用 factorial:(I)I
13: imul // 将栈顶两int类型数相乘,结果入栈
14: ireturn // 返回栈顶数据
LineNumberTable:
line 13: 0
line 14: 4
line 16: 6
StackMapTable: number_of_entries = 1
frame_type = 6 /* same */
}
SourceFile: "ByteCodeView.java"