一个求水仙花数的类的字节码分析
2021-08-06 本文已影响0人
冬天里的懒喵
1.源码
代码如下:
package com.dhb.geektimestudy.kimmking.week1;
public class Hello {
private static final int min = 100;
private static final int max = 1000;
public static void main(String[] args) {
Hello.findLotus();
}
public void findLotus() {
for(int i=min;i<max;i++) {
int first = i/100;
int second = i/10%10;
int third = i%10;
if(first*first*first + second*second*second + third*third*third == i) {
System.out.println(i);
}
}
}
}
2.字节码
使用javap得到字节码:
javap -verbose com.dhb.geektimestudy.kimmking.week1.Hello
Classfile /D:/workspace-mashibing/geektime-study/build/classes/java/main/com/dhb/geektimestudy/kimmking/week1/Hello.class
Last modified 2021-8-5; size 917 bytes
MD5 checksum 57013feb11612a9db90c9b078976db4f
Compiled from "Hello.java"
public class com.dhb.geektimestudy.kimmking.week1.Hello
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#33 // java/lang/Object."<init>":()V
#2 = Methodref #3.#34 // com/dhb/geektimestudy/kimmking/week1/Hello.findLotus:()V
#3 = Class #35 // com/dhb/geektimestudy/kimmking/week1/Hello
#4 = Fieldref #36.#37 // java/lang/System.out:Ljava/io/PrintStream;
#5 = Methodref #38.#39 // java/io/PrintStream.println:(I)V
#6 = Class #40 // java/lang/Object
#7 = Utf8 min
#8 = Utf8 I
#9 = Utf8 ConstantValue
#10 = Integer 100
#11 = Utf8 max
#12 = Integer 1000
#13 = Utf8 <init>
#14 = Utf8 ()V
#15 = Utf8 Code
#16 = Utf8 LineNumberTable
#17 = Utf8 LocalVariableTable
#18 = Utf8 this
#19 = Utf8 Lcom/dhb/geektimestudy/kimmking/week1/Hello;
#20 = Utf8 main
#21 = Utf8 ([Ljava/lang/String;)V
#22 = Utf8 args
#23 = Utf8 [Ljava/lang/String;
#24 = Utf8 MethodParameters
#25 = Utf8 findLotus
#26 = Utf8 first
#27 = Utf8 second
#28 = Utf8 third
#29 = Utf8 i
#30 = Utf8 StackMapTable
#31 = Utf8 SourceFile
#32 = Utf8 Hello.java
#33 = NameAndType #13:#14 // "<init>":()V
#34 = NameAndType #25:#14 // findLotus:()V
#35 = Utf8 com/dhb/geektimestudy/kimmking/week1/Hello
#36 = Class #41 // java/lang/System
#37 = NameAndType #42:#43 // out:Ljava/io/PrintStream;
#38 = Class #44 // java/io/PrintStream
#39 = NameAndType #45:#46 // println:(I)V
#40 = Utf8 java/lang/Object
#41 = Utf8 java/lang/System
#42 = Utf8 out
#43 = Utf8 Ljava/io/PrintStream;
#44 = Utf8 java/io/PrintStream
#45 = Utf8 println
#46 = Utf8 (I)V
{
public com.dhb.geektimestudy.kimmking.week1.Hello();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/dhb/geektimestudy/kimmking/week1/Hello;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: invokestatic #2 // Method findLotus:()V
3: return
LineNumberTable:
line 9: 0
line 10: 3
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 args [Ljava/lang/String;
MethodParameters:
Name Flags
args
public static void findLotus();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=4, args_size=0
0: bipush 100
2: istore_0
3: iload_0
4: sipush 1000
7: if_icmpge 62
10: iload_0
11: bipush 100
13: idiv
14: istore_1
15: iload_0
16: bipush 10
18: idiv
19: bipush 10
21: irem
22: istore_2
23: iload_0
24: bipush 10
26: irem
27: istore_3
28: iload_1
29: iload_1
30: imul
31: iload_1
32: imul
33: iload_2
34: iload_2
35: imul
36: iload_2
37: imul
38: iadd
39: iload_3
40: iload_3
41: imul
42: iload_3
43: imul
44: iadd
45: iload_0
46: if_icmpne 56
49: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
52: iload_0
53: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
56: iinc 0, 1
59: goto 3
62: return
LineNumberTable:
line 14: 0
line 15: 10
line 16: 15
line 17: 23
line 18: 28
line 19: 49
line 14: 56
line 22: 62
LocalVariableTable:
Start Length Slot Name Signature
15 41 1 first I
23 33 2 second I
28 28 3 third I
3 59 0 i I
StackMapTable: number_of_entries = 3
frame_type = 252 /* append */
offset_delta = 3
locals = [ int ]
frame_type = 52 /* same */
frame_type = 250 /* chop */
offset_delta = 5
}
SourceFile: "Hello.java"
3.字节码分析
3.1 常量池及类属性部分
版本号:
minor version: 0 //次版本号 0000
major version: 52 //主版本号 1.8
flags: ACC_PUBLIC, ACC_SUPER //类访问标识 public
//常量池内容 从#1开始,#0的常量池有特殊作用
Constant pool:
#1 = Methodref #6.#33 // java/lang/Object."<init>":()V
#2 = Methodref #3.#34 // com/dhb/geektimestudy/kimmking/week1/Hello.findLotus:()V
#3 = Class #35 // com/dhb/geektimestudy/kimmking/week1/Hello
#4 = Fieldref #36.#37 // java/lang/System.out:Ljava/io/PrintStream;
#5 = Methodref #38.#39 // java/io/PrintStream.println:(I)V
#6 = Class #40 // java/lang/Object
#7 = Utf8 min
#8 = Utf8 I
#9 = Utf8 ConstantValue
#10 = Integer 100
#11 = Utf8 max
#12 = Integer 1000
#13 = Utf8 <init>
#14 = Utf8 ()V
#15 = Utf8 Code
#16 = Utf8 LineNumberTable
#17 = Utf8 LocalVariableTable
#18 = Utf8 this
#19 = Utf8 Lcom/dhb/geektimestudy/kimmking/week1/Hello;
#20 = Utf8 main
#21 = Utf8 ([Ljava/lang/String;)V
#22 = Utf8 args
#23 = Utf8 [Ljava/lang/String;
#24 = Utf8 MethodParameters
#25 = Utf8 findLotus
#26 = Utf8 first
#27 = Utf8 second
#28 = Utf8 third
#29 = Utf8 i
#30 = Utf8 StackMapTable
#31 = Utf8 SourceFile
#32 = Utf8 Hello.java
#33 = NameAndType #13:#14 // "<init>":()V
#34 = NameAndType #25:#14 // findLotus:()V
#35 = Utf8 com/dhb/geektimestudy/kimmking/week1/Hello
#36 = Class #41 // java/lang/System
#37 = NameAndType #42:#43 // out:Ljava/io/PrintStream;
#38 = Class #44 // java/io/PrintStream
#39 = NameAndType #45:#46 // println:(I)V
#40 = Utf8 java/lang/Object
#41 = Utf8 java/lang/System
#42 = Utf8 out
#43 = Utf8 Ljava/io/PrintStream;
#44 = Utf8 java/io/PrintStream
#45 = Utf8 println
#46 = Utf8 (I)V
3.2 findLotus() 方法
descriptor: ()V //主要描述方法的参数和返回值进行描述,改方法没有参数和返回值,因此为()V
flags: ACC_PUBLIC, ACC_STATIC //访问标识,说明该方法时 public static修饰
Code:
stack=3, locals=4, args_size=0 //stack深度为3,本地变量为4 由于方法没有输入参数,因此args_size=0
关于code中 指令部分,用下表来分析
指令 | 程序计数器 | 局部变量表 | stack | 说明 |
---|---|---|---|---|
0: bipush 100 | 0 | < >< >< >< > | 100 | 将100压入stack顶 |
2: istore_0 | 2 | <100><><><> | 将stack顶部的100写入局部变量表中index为0的位置 | |
3: iload_0 | 3 | <100>< >< >< > | 100 | 将局部变量表#0的数据load到stack顶 |
4: sipush 1000 | 4 | <100>< >< >< > | 1000->100 | 将1000压入stack顶部 |
7: if_icmpge 62 | 7 | <100>< >< >< > | 1000->100 | 判断stack中的两个数据,栈底数据100是否大于等于栈顶1000,如果满足则跳转到程序计数器为62的指令 |
10: iload_0 | 10 | <100>< >< >< > | 100 | 将局部变量表中#0load到stack顶部 |
11: bipush 100 | 11 | <100>< >< >< > | 100->100 | 将100压入stack顶部 |
13: idiv | 13 | <100>< >< >< > | 1 | 将stack底部的数据除以stack顶部的数据,得到的结果存在stack顶部 |
14: istore_1 | 14 | <100><1>< >< > | 将stack顶部的数据写入到局部变量表的#1位置 | |
15: iload_0 | 15 | <100><1 >< >< > | 100 | 将局部变量表中#0load到stack顶部 |
16: bipush 10 | 16 | <100><1>< >< > | 10->100 | 将10压入stack顶部 |
18: idiv | 18 | <100><1 >< >< > | 10 | 将stack底部的数据100除以stack顶部的数据10,得到的结果10存在stack顶部 |
19: bipush 10 | 19 | <100><1>< >< > | 10->10 | 将10压入stack顶部 |
21: irem | 20 | <100><1 >< >< > | 0 | 将stack底部的数据10与stack顶部的数据10取余,得到的结果0存在stack顶部 |
22: istore_2 | 22 | <100><1><0>< > | 将stack顶部数据0存入局部变量表的#2位置 | |
23: iload_0 | 23 | <100><1><0>< > | 100 | 将局部变量表#0的数据load到stack顶 |
24: bipush 10 | 24 | <100><1><0>< > | 10->100 | 将10压入stack顶部 |
26: irem | 26 | <100><1><0>< > | 0 | 将stack底部的数据10与stack顶部的数据10取余,得到的结果0存在stack顶部 |
27: istore_3 | 27 | <100><1><0><0> | 将stack顶部数据0存入局部变量表的#3位置 | |
28: iload_1 | 28 | <100><1><0><0> | 1 | 将局部变量表#1的数据load到stack顶 |
29: iload_1 | 29 | <100><1><0><0> | 1->1 | 将局部变量表#1的数据再次load到stack顶 |
30: imul | 30 | <100><1><0><0> | 1 | 将stack底部的数据1与stack顶部的数据1求积,得到的结果1存在stack顶部 |
31: iload_1 | 31 | <100><1><0><0> | 1->1 | 将局部变量表#1的数据load到stack顶 |
32: imul | 32 | <100><1><0><0> | 1 | stack底部的数据1与stack顶部的数据1求积,得到的结果1存在stack顶部 |
33: iload_2 | 33 | <100><1><0><0> | 0-> 1 | 将局部变量表#2的数据load到stack顶 |
34: iload_2 | 34 | <100><1><0><0> | 0->0->1 | 将局部变量表#2的数据load到stack顶 |
35: imul | 35 | <100><1><0><0> | 0->1 | 将stack顶部的两个数据0,0求积,其结果0存在stack顶部 |
36: iload_2 | 36 | <100><1><0><0> | 0->0->1 | 将局部变量表#2的数据load到stack顶 |
37: imul | 37 | <100><1><0><0> | 0->1 | 将stack顶部的两个数据0,0求积,其结果0存在stack顶部 |
38: iadd | 38 | <100><1><0><0> | 1 | 将stack顶部两个数据0,1求和,其结果存在stack顶部 |
39: iload_3 | 39 | <100><1><0><0> | 0->1 | 将局部变量表#3的数据load到stack顶 |
40: iload_3 | 40 | <100><1><0><0> | 0->0->1 | 将局部变量表#3的数据再次load到stack顶 |
41: imul | 41 | <100><1><0><0> | 0->1 | 将stack顶部两个数据0,0求积,其结果0存在stack顶部 |
42: iload_3 | 42 | <100><1><0><0> | 0->0->1 | 将局部变量表#3的数据load到stack顶 |
43: imul | 43 | <100><1><0><0> | 0->1 | 将stack顶部两个数据0,0求积,其结果0存在stack顶部 |
44: iadd | 44 | <100><1><0><0> | 1 | 将stack顶部两个数据0,1求和,其结果1存在stack顶部 |
45: iload_0 | 45 | <100><1><0><0> | 100->1 | 将局部变量表#0的数据load到stack顶 |
46: if_icmpne 56 | 56 | <100><1><0><0> | 比较stack顶部两个数据是否相等,如果不等则跳转到56对应的字节码的指令,此出不等,则会发生跳转 | |
49: getstatic #4 | 49 | <100><1><0><0> | 调用静态方法,方法名为常量池的#4位置,即 java/lang/System.out:Ljava/io/PrintStream; | |
52: iload_0 | 52 | <100><1><0><0> | 局部变量表#0所在的值 | 将局部变量表#0load |
53: invokevirtual #5 | 53 | <100><1><0><0> | 局部变量表#0所在的值 | 调用实例方法,方法名在常量池的#5,即java/io/PrintStream.println:(I)V |
56: iinc 0, 1 | 56 | <101><1><0><0> | 将局部变量表#0的变量增加1 ,变成了101 | |
59: goto 3 | 3 | <101><1><0><0> | 跳转到3对应的字节码 | |
62: return | 62 | < >< >< >< > | 无返回值,方法结束 |
上表中即是方法findLotus的核心计算过程的字节码及对应的程序计数器、局部变量表、stack的执行全过程。
上述过程中只对所有指令做了一次描述,对于goto之后的过程都省略了。实际上执行的过程则会根据执行的if判断和goto进行跳转。
剩余部分字节码:
//行号表
LineNumberTable:
line 14: 0
line 15: 10
line 16: 15
line 17: 23
line 18: 28
line 19: 49
line 14: 56
line 22: 62
//方法的局部变量描述,也就是代码中定义的局部变量名
LocalVariableTable:
Start Length Slot Name Signature
15 41 1 first I
23 33 2 second I
28 28 3 third I
3 59 0 i I
//stack图的属性 为了提高JVM在类型检查的验证过程的效率
StackMapTable: number_of_entries = 3
frame_type = 252 /* append */
offset_delta = 3
locals = [ int ]
frame_type = 52 /* same */
frame_type = 250 /* chop */
offset_delta = 5
3.3 构造函数
在生成的字节码中,可以看到生成了Hello默认的无参构造函数。
public com.dhb.geektimestudy.kimmking.week1.Hello();
//无输入参数和返回值
descriptor: ()V
//访问标识 public
flags: ACC_PUBLIC
Code:
//stack深度1,局部变量1,args为1
stack=1, locals=1, args_size=1
//将局部变量load到stack
0: aload_0
//调用Object的init方法
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
//行号表
LineNumberTable:
line 3: 0
//本地变量表
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/dhb/geektimestudy/kimmking/week1/Hello;
3.4 main方法
public static void main(java.lang.String[]);
//main输入参数 string数组
descriptor: ([Ljava/lang/String;)V
//访问标识 public static
flags: ACC_PUBLIC, ACC_STATIC
Code:
//stack深度为0 局部变量1
stack=0, locals=1, args_size=1
//调用 findLotus 方法
0: invokestatic #2 // Method findLotus:()V
3: return
//行号表
LineNumberTable:
line 9: 0
line 10: 3
//局部变量表
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 args [Ljava/lang/String;
MethodParameters:
Name Flags
args