字节码指令分析简介(五)
### **前言简介**前文已经对虚拟机进行过了简单的介绍,并且也对class文件结构,以及字节码指令进行了详尽的说明
想要了解JVM的运行机制,以及如何优化你的代码,你还需要了解一下,java编译器到底是如何编译你的代码的
本文不是从最底层的编译原理讲解
本文是针对java代码,去查看归纳总结编译器的结果行为,从而直观的感受到字节码指令集
也就是说本文的内容,主要针对的是使用javap 查看字节码文件中方法的code属性中的字节码内容
让你从java代码class文件格式,以及字节码指令集 进行一个直观的演示
提醒:
如果你对字节码指令不了解,而且,没有看过前面的文章,本文可能会轻度不适.
本文示例只是为了展示
您应该经常查看你自己的代码的class文件去发现其中的规律
**一条普通的指令格式**
`<index> <opcode> [ <operand1> [ <operand2>... ]] [<comment>]`
index 表示偏移量 行号等
opcode 表示操作码
operandX表示操作数
comment 为注释
例如,下图所示
行号 0 , 操作码 getstatic ,操作数 #24注释为 Field java/lang/System.
!(data/attachment/forum/202208/30/133631df77yz5v0r1v2xa2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
其中 index行号/偏移量可以作为控制跳转指令的跳转目标比如 goto8 表示跳转到索引为8的指令上
还有一点需要注意的是,javap查看到的内容,你可以认为是class文件表述的信息
但是绝不能理解为就是class文件中的内容
class文件中没有操作码的助记符
比如,getstatic ,都是指令的二进制值
再比如刚才说到的,跳转到指定行号,对于控制转移指令,实际的操作数是在当前指令的操作码集合中的地址偏移量
并不是那个8
只不过javap工具按照更适合我们阅读的方式进行了翻译
### 加载存储与算数指令
```java
public static void main(String[] args) {
int i = -1;
int j = 3;
int k = 5;
int l = 127;
int m = 32767;
int n = 32768;
int add = i+j;
int sub = i-j;
int mul = j*k;
int div = j/k;
int rem = k%j;
int neg = ~j;
int inc = i++;
}
```
!(data/attachment/forum/202208/30/133751w00b8fmc2m8o9xqo.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
-1 ~ 5 使用const加载到操作数栈 其中-1 使用iconst_m1
-128-127 使用bipush
-32768-32767使用sipush
其余常量池ldc
store从操作数栈保存到局部变量表
load加载局部变量到操作数栈
```java
0. 常量-1 加载到操作数栈
1. 操作数栈保存到1号局部变量表 也就是 i = -1;
2. 常量 3 加载到操作数栈
3. 操作数栈保存到2号局部变量表 也就是j = 3;
4. 常量 5 加载到操作数栈
5. 操作数栈保存到3号局部变量表 也就是k =5;
6. 常量 127 加载到操作数栈
8. 操作数栈保存到4号局部变量表 也就是l = 127;
10.常量 32767 加载到操作数栈
13.操作数栈保存到5号局部变量表 也就是m = 32767;
15.加载#17号常量池数据到操作数栈
17. 操作数栈保存到6号局部变量表 也就是n = 32768;
```
!(data/attachment/forum/202208/30/133910tofif055zqq9i8k0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
```java
19. 加载1号局部变量到操作数栈 对应 i
20. 加载2号局部变量到操作数栈对应 j
21. 执行iadd指令计算并将结果压入栈顶 对应 i+j;
22. 保存栈顶元素到7号局部变量
24. 加载1号局部变量到操作数栈 对应 i
25. 加载2号局部变量到操作数栈对应 j
26.执行isub指令计算并将结果压入栈顶 对应i-j;
27. 保存栈顶元素减法结果到8号局部变量
29,30 加载 2号和3号局部变量到操作数栈 也就是j k
31执行imul指令并将结果压栈 j*k
32 保存栈顶元素乘法结果到9号局部变量
34.35 加载 2号和3号局部变量到操作数栈 也就是j k
36 执行idiv 结果压入栈顶
37保存idiv结果到10号局部变量
39.40 加载3号 和 2号 也就是k j
41 执行求余irem 结果压入栈顶
42 栈顶元素结果保存到11号局部变量
44加载2号局部变量对应 j 到操作数栈
45 加载常量-1到操作数栈
46 执行异或运算结果压入栈顶(~x = -1 ^ x;)
47栈顶结果保存到12号局部变量
49 加载1号局部变量 对应 i
50 执行增量 1 计算 结果压入栈顶
53 栈顶结果保存到13号变量
55 void方法 return返回
```
### **类型转换指令**
```java
public static void main(String[] args) {
boolean bNum = true;
char cNum = 2;
byte byteNum = 127;
short sNum = 32767;
int iNum = 100;
long lNum = 65536;
float fNum = 2.5f;
double dNum = 6.8;
char c1 = (char)byteNum;
char c2 = (char)sNum;
char c3 = (char)iNum;
char c4 = (char)lNum;
char c5 = (char)fNum;
char c6 = (char)dNum;
byte b1 = (byte)cNum;
byte b2 = (byte)sNum;
byte b3 = (byte)iNum;
byte b4 = (byte)lNum;
byte b5 = (byte)fNum;
byte b6 = (byte)dNum;
short s1 = (short)cNum;
short s2 = (short)byteNum;
short s3 = (short)iNum;
short s4 = (short)lNum;
short s5 = (short)fNum;
short s6 = (short)dNum;
int i1 = (int)cNum;
int i2 = (int)byteNum;
int i3 = (int)sNum;
int i4 = (int)lNum;
int i5 = (int)fNum;
int i6 = (int)dNum;
long l1 = (long)byteNum;
long l2 = (long)cNum;
long l3 = (long)sNum;
long l4 = (long)iNum;
long l5 = (long)fNum;
long l6 = (long)dNum;
float f1 = (float)byteNum;
float f2 = (float)cNum;
float f3 = (float)sNum;
float f4 = (float)iNum;
float f5 = (float)lNum;
float f6 = (float)dNum;
double d1 = (double)byteNum;
double d2 = (double)cNum;
double d3 = (double)sNum;
double d4 = (double)iNum;
double d5 = (double)lNum;
double d6 = (double)fNum;
}
```
javap解析后的内容太长,接下来分段解析
#### **数据的加载与存储**
!(data/attachment/forum/202208/30/134034zhaf96zkkhagyg9k.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
从数据的存储可以看得出来 boolean内部使用的是数值1也就是1 表示true
#### 数据类型转换为char类型
char byte short int内部形式均为int所以转换为char是,使用的全都是 i2c
longfloat double 先转换为int(l2i f2i d2i) 然后在统一使用 i2c 转换为char
!(data/attachment/forum/202208/30/134107gfurbwwwnzsop9wz.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
#### 数据类型转换为byte 类型
char byte short int内部形式均为int所以转换为byte时,使用的全都是 i2b
longfloat double 先转换为int(l2i f2i d2i) 然后在统一使用 i2b 转换为byte
!(data/attachment/forum/202208/30/134129ahznxcd1vkhbzve7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
#### 数据类型转换为short 类型
还是同样的道理,char byte short int内部形式均为int所以转换为short 使用的是 i2s
longfloat double 先转换为int(l2i f2i d2i) 然后在统一使用 i2s 转换为short
!(data/attachment/forum/202208/30/134150hlg2dihcplugpdup.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
#### 数据类型转换为int 类型
char byte short内部都是int类型.将他们转换为int时,不需要进行转换
如下图所示,一个load 对应一个store
longfloat double (l2i f2i d2i) 转换为int
!(data/attachment/forum/202211/08/143527c5bpp5k4fz5rkagf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
#### **数据类型转换为long 类型**
**char byte shortint 内部都是int类型。将他们转换为long 时,使用i2l**
float double 转换为long f2l d2l
!(data/attachment/forum/202211/08/143559ry33n36goo6zblga.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
#### **数据类型转换为float 类型**
**char byte shortint 内部都是int类型。将他们转换为float 时,使用i2f**
long double 转换为float l2fd2f
!(data/attachment/forum/202211/08/143617xjnwq448rdjwnwrx.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
#### 数据类型转换为double 类型
**char byte shortint 内部都是int类型。将他们转换为double 时,使用i2d**
long
float 转换为double l2df2d
!(data/attachment/forum/202211/08/143639nzz0k0bwjwg8lqmj.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
### 类相关指令
```java
class Super{
}
class Sub extends Super{
}
new Object();
new Super();
Super s = new Super();
new Double(1.5);
new Sub();
Sub sub = new Sub();
```
!(data/attachment/forum/202211/08/143919ruw8bj50t8zu8stj.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
new Object();
new Super();
没有赋值给局部变量 仅仅是创建对象调用new之后,堆中对象的引用保存在栈顶
然后调用构造方法invokespecial
Super s = new Super();
同上面的,需要调用new
因为还需要保存到局部变量
所以new之后 先copy一个,也就是dup
然后调用构造方法 invokespecial
然后从操作数栈保存到局部变量 store
```java
Super super1 = new Super();
Sub sub = new Sub();
//父类引用可以指向子类
//子类引用不能指向父类
//但是对于指向子类的父类引用 可以通过类型转换为子类
Super subToSuper = sub;
Sub superToSub = (Sub) subToSuper;
```
!(data/attachment/forum/202211/08/143902vugoexw5gjlot55t.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
0 创建Spper
3 复制
4 调用构造方法
7 保存到1号局部变量
8 创建Sub
11 复制
12调用构造方法
15 保存到2号局部变量
16 2号加载到操作数栈
17保存到3号局部变量
18加载3号局部变量到栈
19 checkcast 进行校验确认是否可以转换为指定类型 否则报错抛 classCastException
22 再次保存到局部变量
### 控制转移指令
```java
void intWhile() {
int i = 0;
while (i < 100) {
i++;
}
}
void intDoWhile() {
int i = 0;
do {
i++;
}
while (i < 100);
}
void intFor() {
int j = 0;
for(int i =0;i<100;i++) {
j++;
}
}
```
!(data/attachment/forum/202211/08/144024zs3wm3slsses52ee.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
!(data/attachment/forum/202211/08/144033lz5l3p5bvtl5u26p.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
!(data/attachment/forum/202211/08/144043hfj280j85a0rtha8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
intWhile()方法
0. 加载常量0 到操作数栈
1.保存操作数栈元素到1号局部变量 i= 0;
2.直接跳转到第8行
8.1号局部变量加载到操作数栈 也就是i 作为第一个元素
9.加载常量100到操作数栈 也就是100作为第二个元素
11.比较大小,如果前者小于后者 也就是如果 i <100 满足 跳转到第5行否则顺序执行到14 return
5.给1号局部变量以增量1 增加
然后 8-->9-->11-->5-->8-->9-->11......往复循环 直到条件不满足,从11 跳转到14 结束
intDoWhile()
0.加载常量0到操作数栈
1.保存常量0 到1号局部变量
2.给1号局部变量以增量1 进行自增
5.1号局部变量加载到操作数栈
6.常量100加载到操作数栈
8,比较大小 如果前者小于后者也就是 1号局部变量 i<100 跳转到第2行
然后进行往复循环,直到条件不满足,然后顺序到return
intFor()
0.加载常量0 到操作数栈
1. 保存栈顶元素到1号局部变量 j=0;
2. 加载常量0到操作数栈
3. 保存栈顶元素到2号局部变量i=0;
4. 跳转到13行
5. 加载2号局部变量到操作数栈
6. 加载常量100到操作数栈
7. 比较大小,如果前者 2号局部变量 i <100 跳转到7
8. 1号局部变量以增量1自增 j++
9. 2号局部变量以增量1 自增 i++
10. 2号局部变量加载到操作数栈
11. 加载常量100到操作数栈
12. 比较大小,如果前者 2号局部变量 i <100 跳转到7
往复循环 如果条件不满足 从16 顺序到19 结束方法 return
```java
public void fun() {
int i = 0;
if(i<2) {
i++;
}else {
i--;
}
}
```
!(data/attachment/forum/202211/08/144146wivscdfavvzz0pij.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
0, 加载常量0 到栈顶
1,保存栈顶元素 (0) 到1号局部变量
2. 加载1号局部变量到栈顶
3. 加载常量2 到栈顶
4,比较
如果大于后者等于跳转到13 然后1号局部变量 自增1 然后下一步顺序到16 return
否则就是顺序执行到7 1号局部变量 增量为-1自增运算 然后到10 ,10为跳转到16 return
### 方法调用相关指令
```java
public void invoker() {
method(2);
}
public void method(int i) {
if(i>5) {
System.out.println(i);
}
}
```
!(data/attachment/forum/202211/08/144224imzm3m3316p3133t.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
invoker()
0,加载0号 局变量到栈 (上面基本都是第一个数据被保存到1号局部变量,0 号其实是被this 占用了)
1,加载常量2 到操作数栈
2.调用实例方法(I)V
5 return
method(int)
0. 加载1号局部变量到操作数栈
1. 加载常量5 到操作数栈
2比较如果小于等于 跳转到12行 直接返回
如果大于
那么顺序执行到5行 out 是类型为PrintStream的 System中的静态变量
8 加载1号局部变量到操作数栈
9 调用实例方法 println是PrintStream的实例方法 使用invokevirtual
### switch 相关
```java
int i = 5;
int j = 6;
switch (i) {
case 1:
j = j + 1;
break;
case 3:
j = j + 2;
break;
case 5:
j = j + 3;
break;
default:
j = j + 4;
}
```
!(data/attachment/forum/202211/08/144319k6xxd446e1n3ll14.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
0,1,2,4 分别将 5 和 6 加载并存储到1号和2号局部变量
5.加载1号局部变量到栈对应 switch (i) {
然后根据tableswitch 表 进行跳转
虽然我们只有1,3,5但是设置了1到5 ,对于2 和 4 直接跳转到default
40: 2号局部变量 +1
顺序到43
43: 跳转到61 return
46: 2号局部变量 +2
顺序到49
49: 跳转到61 return
52: 2号局部变量 +3
顺序到55
55: 跳转到61 return
58 2号局部变量 +4
顺序到61 return
```java
int j = 6;
String string = "hehe";
switch (string) {
case "A":
j = j + 1;
break;
case "hehe":
j = j + 2;
break;
case "C":
j = j + 3;
break;
default:
j = j + 4;
}
```
!(data/attachment/forum/202211/08/144410d3gnz77bcnb6x8fn.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
0加载常量6到栈
1 保存到 1 号局部变量
3.加载常量池 #36 到栈
!(data/attachment/forum/202211/08/144428r0wccq00xa2qqd2c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
5 保存到2 号局部变量
6 加载2号局部变量 到栈
7 复制栈顶元素
8 复制的元素保存到3号局部变量
9 调用String实例化方法hashCode
12, lookupswitch表中,不在类似tableswitch 了,那个是连续的
lookupswitch是不连续的
我们总共有三个case一个default
lookupswitch 总共有4项
"A" 的hashCode为 65
"C" 的hashCode为 67
"hehe" 的hashCode为 3198650不信的话,自己写个main方法打印下
经过12行 路由之后跳转到指定的序列
你会发现三个case他们的过程是一样的
加载3号局部变量 ,然后将常量 A Chehe 也加载到栈
然后调用equal方法进行比较
!(data/attachment/forum/202211/08/144443yec3ozk8ky12ovyx.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
代码千千万,本文只是找一些基本的示例展示字节码与代码的对应关系,想要熟悉这块
唯有没事多javap看看你代码的class文件,才能通宵领悟,进而更好地优化你的代码
比如看看下面的一个很典型的例子
```java
int i = 5;
int j = 8;
int k = i+j;
int l = 3+6;
```
!(data/attachment/forum/202211/08/144526axc062nr62inx2s4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
前一部分:
0. 常量5 加载到栈
1,保存到 1号局部变量
2. 常量8 加载到栈
4 保存到2号 局部变量
5,加载1号局部变量
6, 加载2号局部变量
7 执行iadd 结果会压入栈顶
8 栈顶元素保存到3号局部变量
至此 完成了前三行代码
后一部分:
9.常量9 加载到栈 (3+6已经被计算好了)
11,保存到4号局部变量
!(data/attachment/forum/202206/16/141330jha7st9soow8772i.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "common_log.png")
`转载务必注明出处:程序员潇然,疯狂的字节X,https://crazybytex.com/thread-186-1-1.html `
页:
[1]