网站建设技术服务税种分类,外包网络推广,随申办app下载,网站建设 盈利前言简介前文已经对虚拟机进行过了简单的介绍,并且也对class文件结构,以及字节码指令进行了详尽的说明想要了解JVM的运行机制,以及如何优化你的代码,你还需要了解一下,java编译器到底是如何编译你的代码的本文不是从最底层的编译原理讲解本文是针对java代码,去查看归纳总结编译…前言简介前文已经对虚拟机进行过了简单的介绍,并且也对class文件结构,以及字节码指令进行了详尽的说明想要了解JVM的运行机制,以及如何优化你的代码,你还需要了解一下,java编译器到底是如何编译你的代码的本文不是从最底层的编译原理讲解本文是针对java代码,去查看归纳总结编译器的结果行为,从而直观的感受到字节码指令集也就是说本文的内容,主要针对的是使用javap 查看字节码文件中方法的code属性中的字节码内容让你从java代码 class文件格式,以及字节码指令集 进行一个直观的演示提醒:如果你对字节码指令不了解,而且,没有看过前面的文章,本文可能会轻度不适.本文示例只是为了展示您应该经常查看你自己的代码的class文件去发现其中的规律一条普通的指令格式 [ [ ... ]] []index 表示偏移量 行号 等opcode 表示操作码operandX表示操作数comment 为注释比如下图所示行号0 , 操作码 getstatic ,操作数 #24 注释为 Fieldjava/lang/System..................其中 index 行号/偏移量 可以作为控制跳转指令的跳转目标 比如 goto 8表示跳转到索引为8的指令上还有一点需要注意的是,javap查看到的内容,你可以认为是class文件表述的信息,但是绝不能理解为就是class文件中的内容比如,class文件中没有操作码的助记符,比如,getstatic,都是指令的二进制值再比如刚才说到的,跳转到指定行号,对于控制转移指令,实际的操作数是在当前指令的操作码集合中的地址偏移量并不是那个8只不过javap工具按照更适合我们阅读的方式进行了翻译加载存储与算数指令public static voidmain(String[] args) {int i -1;int j 3;int k 5;int l 127;int m 32767;int n 32768;int add ij;int sub i-j;int mul j*k;int div j/k;int rem k%j;int neg ~j;int inc i;}-1 ~ 5使用const加载到操作数栈 其中-1 使用iconst_m1-128~127 使用bipush-32768~32767使用sipush其余常量池ldcstore从操作数栈保存到局部变量表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;19. 加载1号局部变量到操作数栈 对应 i20.加载2号局部变量到操作数栈 对应 j21. 执行iadd指令计算并将结果压入栈顶 对应 ij;22.保存栈顶元素到7号局部变量24. 加载1号局部变量到操作数栈 对应 i25. 加载2号局部变量到操作数栈 对应j26.执行isub指令计算并将结果压入栈顶 对应i-j;27. 保存栈顶元素减法结果到8号局部变量29,30加载 2号和3号局部变量到操作数栈 也就是j k31 执行imul指令并将结果压栈 j*k32保存栈顶元素乘法结果到9号局部变量34.35 加载 2号和3号局部变量到操作数栈 也就是j k36 执行idiv结果压入栈顶37保存idiv结果到10号局部变量39.40 加载3号 和 2号 也就是k j41 执行求余irem结果压入栈顶42 栈顶元素结果保存到11号局部变量44加载2号局部变量 对应 j 到操作数栈45加载常量-1到操作数栈46 执行异或运算结果压入栈顶 (~x -1 ^ x;)47栈顶结果保存到12号局部变量49加载1号局部变量 对应 i50 执行增量 1 计算 结果压入栈顶53 栈顶结果保存到13号变量55 void方法return返回类型转换指令public static voidmain(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解析后的内容太长,接下来分段解析数据的加载与存储从数据的存储可以看得出来boolean内部使用的是数值1 也就是1 表示true数据类型转换为char类型char byte short int 内部形式均为int 所以转换为char是,使用的全都是 i2clong float double 先转换为int(l2i f2i d2i) 然后在统一使用i2c 转换为char数据类型转换为byte 类型char byte short int 内部形式均为int 所以转换为byte时,使用的全都是 i2blong float double 先转换为int(l2i f2id2i) 然后在统一使用 i2b 转换为 byte数据类型转换为short 类型还是同样的道理,char byte short int 内部形式均为int 所以转换为short使用的是 i2slong float double 先转换为int(l2i f2id2i) 然后在统一使用 i2s 转换为 short数据类型转换为int 类型char byteshort内部都是int类型.将他们转换为int时,不需要进行转换如下图所示,一个load 对应一个storelong float double (l2if2i d2i) 转换为int数据类型转换为long 类型char byte short int 内部都是int类型.将他们转换为long时,使用 i2lfloat double 转换为long f2l d2l数据类型转换为float 类型char byte short int 内部都是int类型.将他们转换为float 时,使用 i2flong double 转换为float l2f d2f数据类型转换为double 类型char byte short int 内部都是int类型.将他们转换为double 时,使用 i2dlongfloat 转换为double l2d f2d类相关指令classSuper{}class Sub extendsSuper{}newObject();newSuper();Super s newSuper();new Double(1.5);newSub();Sub sub new Sub();new Object();new Super();没有赋值给局部变量 仅仅是创建对象 调用new之后,堆中对象的引用保存在栈顶然后调用构造方法invokespecialSuper s new Super();同上面的,需要调用new因为还需要保存到局部变量所以new之后 先copy一个,也就是dup然后调用构造方法 invokespecial然后从操作数栈保存到局部变量 storeSuper super1 newSuper();Sub sub newSub();//父类引用可以指向子类//子类引用不能指向父类//但是对于指向子类的父类引用 可以通过类型转换为子类Super subToSupersub;Sub superToSub (Sub) subToSuper;0创建Spper3 复制4 调用构造方法7 保存到1号局部变量8 创建Sub11 复制12调用构造方法15保存到2号局部变量16 2号加载到操作数栈17保存到3号局部变量18加载3号局部变量到栈19 checkcast进行校验确认是否可以转换为指定类型 否则报错抛 classCastException22 再次保存到局部变量控制转移指令voidintWhile() {int i 0;while (i 100) {i;}}voidintDoWhile() {int i 0;do{i;}while (i 100);}voidintFor() {int j 0;for(int i 0;i100;i) {j;}}intWhile()方法0.加载常量0 到操作数栈1.保存操作数栈元素到1号局部变量 i 0;2.直接跳转到第8行8.1号局部变量加载到操作数栈 也就是i作为第一个元素9.加载常量100到操作数栈 也就是100作为第二个元素11.比较大小,如果前者小于后者 也就是如果 i 100 满足跳转到第5行 否则顺序执行到14 return5.给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号局部变量 i100 跳转到第2行然后进行往复循环,直到条件不满足,然后顺序到returnintFor()0. 加载常量0 到操作数栈1. 保存栈顶元素到1号局部变量 j0;2. 加载常量0到操作数栈3. 保存栈顶元素到2号局部变量i0;4. 跳转到13行13. 加载2号局部变量到操作数栈14. 加载常量100到操作数栈16. 比较大小,如果前者 2号局部变量 i 100跳转到77. 1号局部变量以增量1 自增 j10. 2号局部变量以增量1 自增i13. 2号局部变量加载到操作数栈14. 加载常量100到操作数栈16. 比较大小,如果前者 2号局部变量 i 100 跳转到7往复循环 如果条件不满足 从16 顺序到19 结束方法 returnpublic voidfun() {int i 0;if(i2) {i;}else{i--;}}0, 加载常量0到栈顶1,保存栈顶元素 (0) 到1号局部变量2. 加载1号局部变量到栈顶3. 加载常量2到栈顶4,比较如果大于后者等于跳转到13 然后1号局部变量 自增1 然后下一步顺序到16 return否则就是顺序执行到7 1号局部变量增量为-1 自增运算 然后到10 ,10为跳转到16 return方法调用相关指令public voidinvoker() {method(2);}public void method(inti) {if(i5) {System.out.println(i);}}invoker()0,加载0号局变量到栈 (上面基本都是第一个数据被保存到1号局部变量,0 号其实是被this 占用了)1,加载常量2到操作数栈2.调用实例方法(I)V5 returnmethod(int)0. 加载1号局部变量到操作数栈1.加载常量5 到操作数栈2比较如果小于等于 跳转到12行 直接返回如果大于那么顺序执行到5行 out 是类型为PrintStream的 System中的静态变量8加载1号局部变量到操作数栈9 调用实例方法 println 是 PrintStream的实例方法使用invokevirtualswitch 相关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;}0,1,2,4分别将 5 和 6 加载并存储到1号和2号局部变量5.加载1号局部变量到栈 对应 switch (i){然后根据tableswitch 表 进行跳转虽然我们只有1,3,5 但是设置了1到5 ,对于2 和 4直接跳转到default40: 2号局部变量 1顺序到4343: 跳转到61 return46: 2号局部变量 2顺序到4949: 跳转到61 return52: 2号局部变量 3顺序到5555: 跳转到61 return58 2号局部变量 4顺序到61returnint 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;}0 加载常量6到栈1 保存到 1 号局部变量3.加载常量池 #36 到栈5 保存到2 号局部变量6 加载2号局部变量到栈7 复制栈顶元素8 复制的元素保存到3号局部变量9 调用String实例化方法hashCode12,lookupswitch表中,不在类似tableswitch 了,那个是连续的lookupswitch 是不连续的我们总共有三个case一个defaultlookupswitch 总共有4项A 的hashCode 为 65C 的hashCode为 67hehe 的hashCode为 3198650 不信的话,自己写个main方法打印下经过12行路由之后跳转到指定的序列你会发现三个case他们的过程是一样的加载3号局部变量 ,然后将常量 A C hehe也加载到栈然后调用equal方法进行比较代码千千万,本文只是找一些基本的示例展示字节码与代码的对应关系,想要熟悉这块唯有没事多javap看看你代码的class文件,才能通宵领悟,进而更好地优化你的代码比如看看下面的一个很典型的例子int i 5;int j 8;int k ij;int l 36;前一部分:0.常量5 加载到栈1,保存到 1号局部变量2. 常量8 加载到栈4 保存到2号 局部变量5,加载1号局部变量6,加载2号局部变量7 执行iadd 结果会压入栈顶8 栈顶元素保存到3号局部变量至此完成了前三行代码后一部分:9.常量9 加载到栈 (36 已经被计算好了)11,保存到4号局部变量