目录:字节码与虚拟机的关系,相当于汇编语言与计算机的关系。当Java源码被编译成Class文件后,虚拟机会将Class文件内的方法字节码载入系统并加以执行;
-
代码如何执行?
- Java字节码在虚拟机中,属于基本执行指令,每个Java字节码指令是一个byte数字,并且有一个对应的助记符
目前所有的字节码指令大约有200余个,比如下面这些: ![](https://img-blog.csdn.net/20170609230058808) |
package hey.up; public class JVMDemo { public int calc(){ int a=500; int b=200; int c=50; return (a+b) /c; } } | 首先我们要编译这段代码,生成class文件: ![](https://img-blog.csdn.net/20170609230059714) 之后在cmd中,来到项目文件夹,通过命令找到这个class文件,命令如下,注意路径: ![](https://img-blog.csdn.net/20170609230059877) |
|
输入这段代码后,会产生如下信息: ![](https://img-blog.csdn.net/20170609230100558) ![](https://img-blog.csdn.net/20170609230101518) | 分析: 该代码首先显示这个Class文件的Java源文件名称,小版本和大版本号; 之后显示该类的常量,有21个; 之后显示方法:第1个方法为类的构造函数,是编译器自动插入的; 第2个方法为calc()方法。在方法体内显示了栈大小,局部变量表大小,字节码指令,行号,局部变量表等消息; | |
|
- 下面来分析红框内,也就是calc()方法的主体内容,看看它的执行过程:
![](https://img-blog.csdn.net/20170609230102245) | 左侧的字节码列表用加粗字体显示了正在执行的字节码,右侧分别显示了当前的局部变量表和操作数栈。在字节码部分,左侧的数字序号表示字节码偏移量,即当前字节码所在的位置,很明显,它不是行号。 字节码偏移量总是和前几个字节码的长度有关,第一条字节码为sipush,自然其偏移量为0。而sipush这条指令本身占用1个字节,但它接受一个双字节的参数,故整个sipush指令合计3个字节码,因此,其后续指令istore_1所在位置在偏移量3处,而istore_1指令为1字节,且不接收参数,故合计1字节,加上之前sipush的3字节,sipush200就在偏移量4的位置,依次类推 |
|
在执行第一条指令的时候,局部变量表第0项为this引用,表示当前对象。对于所有的非静态函数调用,为了能顺利访问this对象,都会将对象的引用放置在局部变量表第0个槽位。指令sipush的作用是将给定的参数压入操作数栈,故执行完sipush500后,操作数栈中含有数字500。 在虚拟机的指令集,还有一条指令为bipush,也是完成相同的功能,但是bipush仅接收一个字节作为其参数,因此,它只能处理-128~127的数字范围,这里的500已经超过了bipush的处理范围,故使用sipush,它可以支持-32768~32767。虚拟机通过这种细分的指令集,可以尽可能减少指令所占的空间,毕竟sipush要比bipush多占一个字节。 |
![](https://img-blog.csdn.net/20170609230102549) | 虚拟机正在执行istore_1命令,store命令是从操作数栈中弹出一个元素,并将其存放在局部变量表中。一般说来,类似像store这样的命令需要带一个参数,用来指明将弹出的元素放在局部变量表的第几个位置。但是,为了尽可能压缩指令大小,使用专门的istore_1指令表示将弹出的元素放置在局部变量表第1个位置。类似的还有istore_0,istore_2,istore_3,它们分别表示从操作数栈顶弹出一个元素,存放在局部变量表第0,2,3个位置。由于局部变量表前几个位置总是非常常用,因此这种做法虽然增加了指令数量,但是可以大大压缩生成的字节码的体积。如果局部变量表很大,需要存储的槽位大于3,那么可以使用istore指令,外加一个参数,用来表示需要存放的槽位位置; 所以在这条指令执行完毕后,操作数栈被清空,局部变量表1的位置存入500; | ![](https://img-blog.csdn.net/20170609230102948) | 接着,执行sipush200,将200压入操作数栈 | ![](https://img-blog.csdn.net/20170609230103705) | 指令istore2,将200存入局部变量第2个位置,并清空操作数栈 | ![](https://img-blog.csdn.net/20170609230104292) | 指令bipush50将50压入操作数栈 | ![](https://img-blog.csdn.net/20170609230104565) | 指令istore_3将50弹出,并存放在局部变量表第3个位置 | ![](https://img-blog.csdn.net/20170609230105495) | 指令iload_1将局部变量表第1个位置的值压入操作数栈。和store指令类似,虚拟机也提供了一系列类似的指令,比如iload_0,iload_2,iload_3等,分别表示将局部变量表第0,2,3个位置的值压入操作数栈。如果需要操作比较大的局部变量表,则可以使用iload,外加一个参数来指明局部变量表的位置。 故在iload_1执行之后,操作数栈中,栈顶元素为500 | ![](https://img-blog.csdn.net/20170609230106330) | 指令iload_2将第2个局部变量压入操作数栈 | ![](https://img-blog.csdn.net/20170609230106792) | 指令iadd表示加法操作,它从操作数栈中弹出两个元素做加法,并将结果再压回操作数栈; | ![](https://img-blog.csdn.net/20170609230107127) | 指令iload_3将局部变量表第3位的50压入操作数栈; | ![](https://img-blog.csdn.net/20170609230108152) | 指令idiv表示整数除法,它从操作数栈中弹出两个元素,相除后将结果压入操作数栈。比iadd略显复杂的是,除法是需要考虑顺序的,idiv的做法是用栈顶第2顺位的严肃除以栈顶元素,故此处为700/50=14 | ![](https://img-blog.csdn.net/20170609230108612) | 最后,通过ireturn,将当前函数操作数栈的顶层元素弹出,并将这个元素压入调用者函数的操作数栈中,所有在当前函数操作数栈中的其他元素都会被丢弃。如果当前返回的是synchroinzed方法,那么还会执行一个隐含的monitorexit指令退出临界区。 最后,会丢弃当前方法的整个帧,恢复调用者的帧,并将控制权转交给调用者。 | |