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

本文探讨了Java字节码与虚拟机之间的关系,比喻为汇编语言与计算机的关系。Java源码被编译成Class文件后,虚拟机加载方法字节码进行执行。Java字节码是虚拟机的基本执行指令,每个指令对应一个byte数字和助记符。通过javap工具可以查看Class文件中的方法字节码,文章以calc()方法为例,解析了其执行过程。
摘要由CSDN通过智能技术生成

目录:字节码与虚拟机的关系,相当于汇编语言与计算机的关系。当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字节,加上之前sipush3字节,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,它们分别表示从操作数栈顶弹出一个元素,存放在局部变量表第023个位置。由于局部变量表前几个位置总是非常常用,因此这种做法虽然增加了指令数量,但是可以大大压缩生成的字节码的体积。如果局部变量表很大,需要存储的槽位大于3,那么可以使用istore指令,外加一个参数,用来表示需要存放的槽位位置;

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

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

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

指令bipush5050压入操作数栈

指令istore_350弹出,并存放在局部变量表第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指令退出临界区。
最后,会丢弃当前方法的整个帧,恢复调用者的帧,并将控制权转交给调用者。

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值