虚拟机可以分为两种:
- 系统虚拟机:利用软件来运行一个完整的操作系统,例如可以借助 VirtualBox 来安装 Linux 系统。
- 程序虚拟机:用于在与平台无关的环境中执行程序,例如 JVM.
本文关注后者。
解释器是执行程序的一种方式,它接受程序源码,输出源码的执行结果。它与编译的区别可以理解为:
但是严格来讲,对于某种语言而言,并无编译型语言与解释型语言之分。例如,Cpp 经常被视为编译型语言,但也有一款名为 Clint 的解释器。
实际上,解释器更像是一个黑箱操作,它为开发者隐藏了编译步骤,从这一角度来看,解释器更像是 编译 + 虚拟机 的结合。
此时,程序虚拟机的含义更明确了一些:它是指执行编译产物的一种环境。
c 的编译产物为二进制,它的执行环境为真实的操作系统; v8 下的 JavaScript 编译产物为字节码,它的执行环境为 v8 虚拟机; ...
为了顺利的执行编译产物,程序虚拟机需要实现一套硬件结构,例如 CPU, stack, register 等;此外,在软件层面上,还需要实现一套指令系统。
程序虚拟机的指令集可以分为两种实现方式:
- 栈;
- 寄存器。
首先来看一段 Java 代码:
public static void main(String[] args) {
int a = 1;
int b = 2;
int c = a + b;
}
首先通过 javac <CodePath>
上述代码编译后,再将其产物通过 javap -verbose -p <YourClass>
将其进行反编译看,即得到相应的 Java 字节码:
其字节码中,有如下一段:
其中:
0: iconst_1
1: istore_1
表示创建一个变量 1,然后存储变量 1.
随后:
4: iload_1
5: iload_2
6: iadd
7: isotre_3
表示,加载变量 1 和变量 2, 然后相加,然后存储。
此时,思考一个问题:最后存储的变量 3 为什么是上一次相加的结果?
答案很简单:因为上次相加的值被压回到栈顶,因此可以拿到该值。其过程示例为:
4: iload_1
5: iload_2
6: iadd ---> result
7: isotre_3 istore_3
上述过程即为栈实现的程序虚拟机(JVM 即为栈实现)
而若是寄存器实现,则可能出现多地址指令,例如 a = b + c
可能变为:
add a, b, c
// 一般形式为:
op dest, src1, src2
上述操作中,dest
, src_x
都是存储在寄存器中的。