先写一段java代码
public class Hello {
public Hello() {
}
public static void main(String[] args) {
int a = 1;
int b = 2;
int c = add(a, b);
String d = "abc";
System.out.println(c + d);
for(int i = 0; i < c; ++i) {
System.out.println(i);
}
}
public static int add(int x, int y) {
return x + y;
}
}
命令行中获取字节码指令 javap -v Hello.class
看下面的指令的时候,哪个指令不知道啥意思,就到这几个链接里查一下,网上找了几篇,官网没找到惭愧。 Java字节码指令收集大全 , 字节码指令
下面开始读下面的指令到底是啥意思
D:\androiddemo\6\ByteCode\lib\build\classes\java\main\com\hsm\lib>javap -v Hello.class
Classfile /D:/androiddemo/6/ByteCode/lib/build/classes/java/main/com/hsm/lib/Hello.class
Last modified 2019-12-11; size 1085 bytes
MD5 checksum 0cca87a281db7758d1c26d2113c3f3ae
Compiled from "Hello.java"
public class com.hsm.lib.Hello
minor version: 0 //次版本号
major version: 51 //主版本号 52代表jdk1.8 , 51代表jdk1.7
flags: ACC_PUBLIC, ACC_SUPER //public类型 , 使用新的invokespecial语义
//常量池(一个类的结构索引,包括类,变量,方法名,方法参数等等,供后面的方法直接引用)
Constant pool:
//Object类型的构造方法,指向13和14位置 <init>代表构造方法 ()V 代表没有参数,没有返回值
#1 = Methodref #13.#41 // java/lang/Object."<init>":()V
//add方法,指向12和42位置,参数为两个int
#2 = Methodref #12.#42 // com/hsm/lib/Hello.add:(II)I
//String类型字面量
#3 = String #43 // abc
// 字段符号引用,指向java/lang/System这个类的out成员变量,变量类型是Ljava/io/PrintStream
#4 = Fieldref #44.#45 // java/lang/System.out:Ljava/io/PrintStream;
//StringBuilder类
#5 = Class #46 // java/lang/StringBuilder
//相当于new一个StringBuilder,StringBuilder类的构造法描述 没参数 没有返回值
#6 = Methodref #5.#41 // java/lang/StringBuilder."<init>":()V
//调用StringBuilder的append方法,参数为int
#7 = Methodref #5.#47 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
//调用StringBuilder的append方法,参数为String
#8 = Methodref #5.#48 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
//调用toString操作
#9 = Methodref #5.#49 // java/lang/StringBuilder.toString:()Ljava/lang/String;
//方法println,参数为String,没有返回值
#10 = Methodref #50.#51 // java/io/PrintStream.println:(Ljava/lang/String;)V
//方法println,参数为int,没有返回
#11 = Methodref #50.#52 // java/io/PrintStream.println:(I)V
#12 = Class #53 // com/hsm/lib/Hello //Hello类
#13 = Class #54 // java/lang/Object //Hello父类Object类
#14 = Utf8 <init> //构造方法
#15 = Utf8 ()V //没有参数,没有返回值
#16 = Utf8 Code //Code属性表,用来存放JVM指令
//行号表(保存JVM指令和源码的映射关系,程序抛异常的时候,可以定位哪一行源码报错)
#17 = Utf8 LineNumberTable
#18 = Utf8 LocalVariableTable //局部变量表
#19 = Utf8 this //传过来的局部变量 this
#20 = Utf8 Lcom/hsm/lib/Hello; //描述属于哪个类
#21 = Utf8 main //main方法的方法名
#22 = Utf8 ([Ljava/lang/String;)V //main方法的描述(参数是String数组,返回是空)
#23 = Utf8 i //局部变量i
#24 = Utf8 I //i是int类型
#25 = Utf8 args //局部变量args
#26 = Utf8 [Ljava/lang/String; //args 是String数组类型
#27 = Utf8 a //局部变量a
#28 = Utf8 b //局部变量b
#29 = Utf8 c //局部变量c
#30 = Utf8 d //局部变量d
#31 = Utf8 Ljava/lang/String; // d是String类型
//栈图表 记录了局部变量的验证类型,字节码的偏移量,操作栈中的验证类型等
#32 = Utf8 StackMapTable
#33 = Class #26 // "[Ljava/lang/String;" //String 数组类
#34 = Class #55 // java/lang/String //String类
#35 = Utf8 add //方法名 add
#36 = Utf8 (II)I //add方法两个int参数,返回值为int
#37 = Utf8 x //局部变量 x
#38 = Utf8 y //局部变量 y
#39 = Utf8 SourceFile //源码文件名称
#40 = Utf8 Hello.java //源码文件名称 Hello.java
//NameAndType结构用来描述一个方法或者成员变量 ,这里表示构造方法,没有参数,返回值为空
#41 = NameAndType #14:#15 // "<init>":()V
#42 = NameAndType #35:#36 // add:(II)I //add方法,参数为两个int,返回值为int
#43 = Utf8 abc //局部变量abc
#44 = Class #56 // java/lang/System //java/lang/System类
//System类中out属性,out的类型是java/io/PrintStream
#45 = NameAndType #57:#58 // out:Ljava/io/PrintStream;
#46 = Utf8 java/lang/StringBuilder //StringBuilder变量
//append方法,参数是int,返回值是StringBuilder
#47 = NameAndType #59:#60 // append:(I)Ljava/lang/StringBuilder;
//append方法,参数是String,返回值是StringBuilder
#48 = NameAndType #59:#61 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
//toString方法,参数是空,返回值是String
#49 = NameAndType #62:#63 // toString:()Ljava/lang/String;
#50 = Class #64 // java/io/PrintStream //PrintStream类
//println方法,参数是String,返回值是空
#51 = NameAndType #65:#66 // println:(Ljava/lang/String;)V
//println方法,参数是int,返回值是空 这个是源码中for循环中的println
#52 = NameAndType #65:#67 // println:(I)V
#53 = Utf8 com/hsm/lib/Hello
#54 = Utf8 java/lang/Object
#55 = Utf8 java/lang/String
#56 = Utf8 java/lang/System
#57 = Utf8 out
#58 = Utf8 Ljava/io/PrintStream;
#59 = Utf8 append
#60 = Utf8 (I)Ljava/lang/StringBuilder;
#61 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#62 = Utf8 toString
#63 = Utf8 ()Ljava/lang/String;
#64 = Utf8 java/io/PrintStream
#65 = Utf8 println
#66 = Utf8 (Ljava/lang/String;)V
#67 = Utf8 (I)V
{
public com.hsm.lib.Hello(); //构造方法
descriptor: ()V //方法描述 没有参数 返回值为空
flags: ACC_PUBLIC // 是public方法
Code: //Code属性表,用来存放JVM指令
//stack 最大操作数栈 1
//locals局部变量所需空间这里就一个this
//args_size 方法参数的个数,这里是1,因为每个实例方法(new)都会有一个隐藏参数this
stack=1, locals=1, args_size=1
//从局部变量0中加载引用变量入栈
0: aload_0
//编译时的方法绑定调用时的方法 这里就是绑定父类的构造方法
1: invokespecial #1 // Method java/lang/Object."<init>":()V
//返回空
4: return
//行号表(保存JVM指令和源码的映射关系,程序抛异常的时候,可以定位哪一行源码报错)
LineNumberTable:
line 8: 0
// 局部变量表,这里就一个this局部变量
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/hsm/lib/Hello;
public static void main(java.lang.String[]); //main方法
descriptor: ([Ljava/lang/String;)V //参数是String数组,返回为空
flags: ACC_PUBLIC, ACC_STATIC //public方法 静态方法
//Code属性表,用来存放JVM指令
Code:
//stack 最大操作数栈 3 JVM运行时会根据这个值来分配栈帧(Frame)中的操作栈深度
//locals局部变量所需空间 6(args,a,b,c,d,i)
//args_size 方法参数的个数,这里是1 args
stack=3, locals=6, args_size=1
0: iconst_1 //int类型常量1入栈
1: istore_1 //将栈顶的int类型数值存入第二个局部变量 a
2: iconst_2 //int类型常量2入栈
3: istore_2 //栈顶int数值存入第3个局部变量 b
4: iload_1 //第2个int型变量进栈
5: iload_2 //第3个int型变量进栈
6: invokestatic #2 // Method add:(II)I //调用静态方法add
9: istore_3 //栈顶int数值存入第4局部变量
10: ldc #3 // String abc //常量池中的常量入栈
12: astore 4 //将栈顶引用型数值存入指定本地变量 abc存入变量d
//获取静态字段,并将其压入栈顶 out
14: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
//创建一个对象,并将其压入栈顶
17: new #5 // class java/lang/StringBuilder
20: dup //复制栈顶数值,并压入栈
//调用需要特殊处理的实例方法 StringBuilder的构造方法
21: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
24: iload_3 // 第4个int型变量进栈 c
//调用实例的方法 StringBuilder的append(int)方法
25: invokevirtual #7 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
28: aload 4 //第一个引用类型变量入栈 d
//调用实例的方法 StringBuilder的append(String)方法
30: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
//调用实例StringBuilder的toString方法
33: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
//调用实例的方法 打印的方法PrintStream.println
36: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
39: iconst_0 //int常量类型0入栈 i
40: istore 5 //栈顶第一个int数值存入第一个变量
42: iload 5 //从局部变量indexbyte中装载int类型值入栈
44: iload_3 //第四个int类型变量入栈
45: if_icmpge 62 //若栈顶两int类型值前大于等于后则跳转。
// //获取静态字段,并将其压入栈顶 out
48: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
51: iload 5 /从局部变量indexbyte中装载int类型值入栈
//执行打印方法
53: invokevirtual #11 // Method java/io/PrintStream.println:(I)V
56: iinc 5, 1 //将指定 int 型变量增加指定值 i++,
59: goto 42 //无条件跳转到指定位置
62: return //void函数返回
//行号表(保存JVM指令和源码的映射关系,程序抛异常的时候,可以定位哪一行源码报错)
LineNumberTable:
line 11: 0
line 12: 2
line 13: 4
line 14: 10
line 15: 14
line 16: 39
line 17: 48
line 16: 56
line 19: 62
//局部变量表
LocalVariableTable:
Start Length Slot Name Signature
42 20 5 i I
0 63 0 args [Ljava/lang/String;
2 61 1 a I
4 59 2 b I
10 53 3 c I
14 49 4 d Ljava/lang/String;
//栈图表 通过记录偏移量来保证字节序,并且不会重复记录
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 42
locals = [ class "[Ljava/lang/String;", int, int, int, class java/lang/String, int ]
stack = []
frame_type = 250 /* chop */
offset_delta = 19
public static int add(int, int); //add 方法
descriptor: (II)I //描述:参数两个int值,返回值int
flags: ACC_PUBLIC, ACC_STATIC //public static
//Code属性表,用来存放JVM指令
Code:
//stack 最大操作数栈 2
//locals局部变量所需空间这2
//args_size 方法参数的个数 2
stack=2, locals=2, args_size=2
0: iload_0
1: iload_1
2: iadd
3: ireturn
//行号表(保存JVM指令和源码的映射关系,程序抛异常的时候,可以定位哪一行源码报错)
LineNumberTable:
line 23: 0
//局部变量表
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 x I
0 4 1 y I
}
SourceFile: "Hello.java"
终于一行一行读完了,总结一下,感觉主要有两大部分:
第一大部分常量池(Constant pool)部分这个主要是用来记录各种常量的,不止是我们定义的各种成员变量、局部变量,还有类名类是否有参数,方法名,方法是否有参数,方法的返回值,还有每个类型是用什么数据类型保存的等这个类的所有信息,供后面的方法调用。
第二大部分方法的执行:方法部分有方法的描述,JVM指令执行顺序,每个指令的意思就得去查指令表了。行号表可以保存JVM指令和源码的映射关系,程序抛异常的时候,可以定位哪一行源码报错,局部变量表存放方法中的局部变量。
其实上面的指令已经是翻译过的了,如果直接用 “010 Editor” 这个编辑器查看.class文件,会看到一堆十六进制的字符。他们也都有自己的意思,这个链接和这个视频可以了解怎么翻译十六进制的字符,每个字符都有自己的含义,一个一个的查表,翻译完之后就是前面的指令代码了。不过玩ASM的话看懂前面的指令码就可以了。
资料: