您的位置:首页 > 编程语言 > Java开发

Java虚拟机--字节码(二十一)

2017-06-09 23:01 169 查看
目录:字节码与虚拟机的关系,相当于汇编语言与计算机的关系。当Java源码被编译成Class文件后,虚拟机会将Class文件内的方法字节码载入系统并加以执行;
   

代码如何执行?
Java字节码在虚拟机中,属于基本执行指令,每个Java字节码指令是一个byte数字,并且有一个对应的助记符

目前所有的字节码指令大约有200余个,比如下面这些:



一个方法的java字节码指令,被编译到Java方法的Code属性中,如果想要查看指令的具体内容,可使用JDK自带的javap工具,javap常用参数如下:



示例:演示javap的使用

 

 

package hey.up;

public class JVMDemo {

public int calc(){
int a=500;
int b=200;
int c=50;
return (a+b) /c;
}
}

首先我们要编译这段代码,生成class文件:



之后在cmd中,来到项目文件夹,通过命令找到这个class文件,命令如下,注意路径:



输入这段代码后,会产生如下信息:





分析:

该代码首先显示这个Class文件的Java源文件名称,小版本和大版本号;

之后显示该类的常量,有21个;

之后显示方法:第1个方法为类的构造函数,是编译器自动插入的;

第2个方法为calc()方法。在方法体内显示了栈大小,局部变量表大小,字节码指令,行号,局部变量表等消息;

下面来分析红框内,也就是calc()方法的主体内容,看看它的执行过程:

 

左侧的字节码列表用加粗字体显示了正在执行的字节码,右侧分别显示了当前的局部变量表和操作数栈。在字节码部分,左侧的数字序号表示字节码偏移量,即当前字节码所在的位置,很明显,它不是行号。

字节码偏移量总是和前几个字节码的长度有关,第一条字节码为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多占一个字节。



虚拟机正在执行istore_1命令,store命令是从操作数栈中弹出一个元素,并将其存放在局部变量表中。一般说来,类似像store这样的命令需要带一个参数,用来指明将弹出的元素放在局部变量表的第几个位置。但是,为了尽可能压缩指令大小,使用专门的istore_1指令表示将弹出的元素放置在局部变量表第1个位置。类似的还有istore_0,istore_2,istore_3,它们分别表示从操作数栈顶弹出一个元素,存放在局部变量表第0,2,3个位置。由于局部变量表前几个位置总是非常常用,因此这种做法虽然增加了指令数量,但是可以大大压缩生成的字节码的体积。如果局部变量表很大,需要存储的槽位大于3,那么可以使用istore指令,外加一个参数,用来表示需要存放的槽位位置;

所以在这条指令执行完毕后,操作数栈被清空,局部变量表1的位置存入500;



接着,执行sipush200,将200压入操作数栈



指令istore2,将200存入局部变量第2个位置,并清空操作数栈



指令bipush50将50压入操作数栈



指令istore_3将50弹出,并存放在局部变量表第3个位置



指令iload_1将局部变量表第1个位置的值压入操作数栈。和store指令类似,虚拟机也提供了一系列类似的指令,比如iload_0,iload_2,iload_3等,分别表示将局部变量表第0,2,3个位置的值压入操作数栈。如果需要操作比较大的局部变量表,则可以使用iload,外加一个参数来指明局部变量表的位置。

故在iload_1执行之后,操作数栈中,栈顶元素为500



指令iload_2将第2个局部变量压入操作数栈



指令iadd表示加法操作,它从操作数栈中弹出两个元素做加法,并将结果再压回操作数栈;



指令iload_3将局部变量表第3位的50压入操作数栈;



指令idiv表示整数除法,它从操作数栈中弹出两个元素,相除后将结果压入操作数栈。比iadd略显复杂的是,除法是需要考虑顺序的,idiv的做法是用栈顶第2顺位的严肃除以栈顶元素,故此处为700/50=14



最后,通过ireturn,将当前函数操作数栈的顶层元素弹出,并将这个元素压入调用者函数的操作数栈中,所有在当前函数操作数栈中的其他元素都会被丢弃。如果当前返回的是synchroinzed方法,那么还会执行一个隐含的monitorexit指令退出临界区。

最后,会丢弃当前方法的整个帧,恢复调用者的帧,并将控制权转交给调用者。

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 虚拟机