[jvm解析系列][十三]字节码指令小节,从字节码看JVM的栈解释器执行过程。

时间:2020-12-09 17:10:59

众所周知,JVM以前一直采用的是解释执行,但是后来在历代的版本更迭中也加入了编译执行。所以总的来说JVM是包含了解释执行和编译执行。这一部分不属于JVM的范畴了,已经属于编译了,大多数都是进行词法分析之类的,以后有时间会补充。

同时大家都知道现在大体上分为两种指令集架构,第一种就是基于栈的第二种是基于寄存器的,简单点说,基于寄存器的架构速度更快,但是可移植性不强,但是基于栈的指令集架构虽然慢,但是可移植性很强,大家都知道java本身就是依靠可移植性出名的,所以无可争议的使用了栈的指令集架构。(也有例外,dalvik是基于寄存器的)

下面我们详述一下JVM的栈解释器执行过程,在此之前我们先来讲一下字节码的指令含义:



加载和存储:加载和存储指令一般可以把栈帧中的局部变量放到操作数栈中,然后把操作数栈中的变量存回栈帧中。

把局部变量加载到操作数栈中:主要有iload,liload_<n>,lload,(每一个指令前面代表的是它操作的数据类型iload就是int   lload就是long,接下来我们去除前面的前缀统一用x代替减少篇幅。)

从操作数栈中存回局部变量表 xstore_<n>,xstore(注意有的后面跟了_<n>这是省略了诸如xstore_1,xstore_2这样的指令,xstore默认为xstore_0,之后统一用xstore_<n>替代)

加载一个常量到操作数栈:xipush,xdc,xconst_<n>



运算指令:执行加减乘除取余等运算

加:xadd

减:xsub

乘:xmul

除:xdiv

取余:xrem

取反:xneg

位移:xshl。xshr

按位或指令:xor

按位与指令:xand

按位异或指令:xxor

比较指令:xcmpl



指令上我们大概就讲这么多,接下来就是我们查看字节码的时刻了。首先写一个方法如下:

public static void main(String[] args) {
// TODO Auto-generated method stub
int a = 2;
int b = 1;
int c = a+b;
System.out.println(c);
}


然后我们用javap查看一下字节码

[jvm解析系列][十三]字节码指令小节,从字节码看JVM的栈解释器执行过程。


可以看到我们的操作从第01两行来看这事int a =2;的操作,先放置常量2到栈顶然后取出来放到常量表中1的位置。23同样。

而45这两个数字就是把局部变量表上12这两个位置的数加载到操作栈中,然后用6行相加存入常量表3的位置。

为了表明0123和7真的实在存储数字到常量表我们对方法做如下修改:

public static void main(String[] args) {
// TODO Auto-generated method stub
int c = 1+2;
System.out.println(c);
}
字节码如下
[jvm解析系列][十三]字节码指令小节,从字节码看JVM的栈解释器执行过程。

很明显的之前的0123行那种存储的行为没有了,同样我们能看到javac给我们的优化,在第0行把1+2直接变成了3。