众所周知,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查看一下字节码
可以看到我们的操作从第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);
}
很明显的之前的0123行那种存储的行为没有了,同样我们能看到javac给我们的优化,在第0行把1+2直接变成了3。