[四] java虚拟机JVM编译器编译代码简介 字节码指令实例 代码到底编译成了什么形式

阅读目录 前言简介 加载存储与算数指令 类型转换指令 类相关指令 控制转移指令 方法调用相关指令 switch 相关 回到顶部 前言简介 前文已经对虚拟机进行过了简单的介绍,并且也对class文件结构,以及字节码指令进行了详尽的说明 想要了解JVM的运行机制,以及如何优化你的代码,你还需要了解一下,java编译器到底是如何编译你的代码的 本文不是从最底层的编译原理讲解 本文是针对java代码,去查看归纳总结编译器的结果行为,从而直观的感受到字节码指令集 也就是说本文的内容,主要针对的是使用javap 查看字节码文件中方法的code属性中的字节码内容 让你从java代码 class文件格式,以及字节码指令集 进行一个直观的演示 提醒: 如果你对字节码指令不了解,而且,没有看过前面的文章,本文可能会轻度不适. 本文示例只是为了展示 您应该经常查看你自己的代码的class文件去发现其中的规律 一条普通的指令格式 [ [ ... ]] [] index 表示偏移量 行号 等 opcode 表示操作码 operandX表示操作数 comment 为注释 比如下图所示 行号 0 , 操作码 getstatic ,操作数 #24 注释为 Field java/lang/System.................. image_5b879224_252b_thumb[1] 其中 index 行号/偏移量 可以作为控制跳转指令的跳转目标 比如 goto 8 表示跳转到索引为8的指令上 还有一点需要注意的是,javap查看到的内容,你可以认为是class文件表述的信息,但是绝不能理解为就是class文件中的内容 比如,class文件中没有操作码的助记符,比如,getstatic ,都是指令的二进制值 再比如刚才说到的,跳转到指定行号,对于控制转移指令,实际的操作数是在当前指令的操作码集合中的地址偏移量 并不是那个8 只不过javap工具按照更适合我们阅读的方式进行了翻译 回到顶部 加载存储与算数指令 复制代码 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++; } 复制代码 image_5b879224_2d18_thumb[1] -1 ~ 5 使用const加载到操作数栈 其中-1 使用iconst_m1 -128~127 使用bipush -32768~32767使用sipush 其余常量池ldc store从操作数栈保存到局部变量表 load加载局部变量到操作数栈 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; image_5b879224_4b5a_thumb[1] 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返回 回到顶部 类型转换指令 复制代码 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解析后的内容太长,接下来分段解析 数据的加载与存储 image_5b879224_1328_thumb[1] 从数据的存储可以看得出来 boolean内部使用的是数值1 也就是1 表示true 数据类型转换为char类型 char byte short int 内部形式均为int 所以转换为char是,使用的全都是 i2c long float double 先转换为int(l2i f2i d2i) 然后在统一使用 i2c 转换为char image_5b879225_45f1_thumb[1] 数据类型转换为byte 类型 char byte short int 内部形式均为int 所以转换为byte时,使用的全都是 i2b long float double 先转换为int(l2i f2i d2i) 然后在统一使用 i2b 转换为 byte image_5b879225_7927_thumb[1] 数据类型转换为short 类型 还是同样的道理,char byte short int 内部形式均为int 所以转换为short 使用的是 i2s long float double 先转换为int(l2i f2i d2i) 然后在统一使用 i2s 转换为 short image_5b879225_7e40_thumb[1] 数据类型转换为int 类型 char byte short内部都是int类型.将他们转换为int时,不需要进行转换 如下图所示,一个load 对应一个store long float double (l2i f2i d2i) 转换为int image_5b879225_4c4a_thumb[1] 数据类型转换为long 类型 char byte short int 内部都是int类型.将他们转换为long 时,使用 i2l float double 转换为long f2l d2l image_5b879225_209d_thumb[1] 数据类型转换为float 类型 char byte short int 内部都是int类型.将他们转换为float 时,使用 i2f long double 转换为float l2f d2f image_5b879225_2e9e_thumb[1] 数据类型转换为double 类型 char byte short int 内部都是int类型.将他们转换为double 时,使用 i2d long float 转换为double l2d f2d image_5b879225_4660_thumb[1] 回到顶部 类相关指令 复制代码 class Super{ } class Sub extends Super{ } new Object(); new Super(); Super s = new Super(); new Double(1.5); new Sub(); Sub sub = new Sub(); 复制代码 image_5b879225_51b9_thumb[1] new Object(); new Super(); 没有赋值给局部变量 仅仅是创建对象 调用new之后,堆中对象的引用保存在栈顶 然后调用构造方法invokespecial Super s = new Super(); 同上面的,需要调用new 因为还需要保存到局部变量 所以new之后 先copy一个,也就是dup 然后调用构造方法 invokespecial 然后从操作数栈保存到局部变量 store 复制代码 Super super1 = new Super(); Sub sub = new Sub(); //父类引用可以指向子类 //子类引用不能指向父类 //但是对于指向子类的父类引用 可以通过类型转换为子类 Super subToSuper = sub; Sub superToSub = (Sub) subToSuper; 复制代码 image_5b879225_2fff_thumb[1] 0 创建Spper 3 复制 4 调用构造方法 7 保存到1号局部变量 8 创建Sub 11 复制 12调用构造方法 15 保存到2号局部变量 16 2号加载到操作数栈 17保存到3号局部变量 18加载3号局部变量到栈 19 checkcast 进行校验确认是否可以转换为指定类型 否则报错抛 classCastException 22 再次保存到局部变量 回到顶部 控制转移指令 复制代码 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++; } } 复制代码 image_5b879225_6bae_thumb[1] 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行 13. 加载2号局部变量到操作数栈 14. 加载常量100到操作数栈 16. 比较大小,如果前者 2号局部变量 i <100 跳转到7 7. 1号局部变量以增量1 自增 j++ 10. 2号局部变量以增量1 自增 i++ 13. 2号局部变量加载到操作数栈 14. 加载常量100到操作数栈 16. 比较大小,如果前者 2号局部变量 i <100 跳转到7 往复循环 如果条件不满足 从16 顺序到19 结束方法 return 复制代码 public void fun() { int i = 0; if(i<2) { i++; }else { i--; } } 复制代码 image_5b879225_6ec9_thumb[1] 0, 加载常量0 到栈顶 1,保存栈顶元素 (0) 到1号局部变量 2. 加载1号局部变量到栈顶 3. 加载常量2 到栈顶 4,比较 如果大于后者等于跳转到13 然后1号局部变量 自增1 然后下一步顺序到16 return 否则就是顺序执行到7 1号局部变量 增量为-1 自增运算 然后到10 ,10为跳转到16 return 回到顶部 方法调用相关指令 复制代码 public void invoker() { method(2); } public void method(int i) { if(i>5) { System.out.println(i); } } 复制代码 image_5b879225_55f1_thumb[1] 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 相关 复制代码 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; } 复制代码 image_5b879225_5641_thumb[1] 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 复制代码 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; } 复制代码 image_5b879226_5e68_thumb[4] 0 加载常量6到栈 1 保存到 1 号局部变量 3.加载常量池 #36 到栈 image_5b879226_6b5c_thumb[1] 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 C hehe 也加载到栈 然后调用equal方法进行比较 image_5b879226_523c_thumb[1] 代码千千万,本文只是找一些基本的示例展示字节码与代码的对应关系,想要熟悉这块 唯有没事多javap看看你代码的class文件,才能通宵领悟,进而更好地优化你的代码 比如看看下面的一个很典型的例子 复制代码 int i = 5; int j = 8; int k = i+j; int l = 3+6; 复制代码 image_5b879226_3a97_thumb[1] 前一部分: 0. 常量5 加载到栈 1,保存到 1号局部变量 2. 常量8 加载到栈 4 保存到2号 局部变量 5,加载1号局部变量 6, 加载2号局部变量 7 执行iadd 结果会压入栈顶 8 栈顶元素保存到3号局部变量 至此 完成了前三行代码 后一部分: 9.常量9 加载到栈 (3+6 已经被计算好了) 11,保存到4号局部变量https://www.cnblogs.com/noteless/p/9560161.html
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信