基于栈的字节码解析执行引擎
虚拟机是如何执行字节码指令的?上文中提到,许多Java虚拟机的执行引擎在执行Java代码的时候都是解析执行(通过解析器执行)和编译器执行(通过即时编译器产生本地代码执行)两种选择,在本章中我们先来探讨一下在解释执行时虚拟机执行引擎是如何工作的。
什么是解析执行?什么是编译执行?
解释执行:就是说我们的Java源代码在被执行的同时通过解析器,将Java源代码一句一句的解释为机器语言(本地机器码),这就有点像“同声翻译”,这样做的坏处就是程序的执行效率比较低,而且应用程序不能脱离期解析器,但这种方式比较灵活,可以动态地调整,修改应用程序,典型的解释型高级语言有BASIC。
编译执行:编译时指在源程序执行之前,就通过编译器将程序源代码“翻译”成目标代码(机器语言),因此其目标程序可以脱离其语言环境独立执行,使用比较方便,效率较高。但应用程序一旦需要修改,必须先修改源代码,再次重新编译生成新的目标文件(*.obj)才能执行,只有目标文件而没有源文件,修改很不方便。现在大多数的编程语言都是编译型的,例如C++等
总结,解析执行,编译执行,各有千秋。
我们的Java语言
Java语言在JDK1.0时代确实算的上是一个“解释执行”的语言,但是当前主流的虚拟机都包含了基石编译器,Class文件中的代码到底是被解析执行还是编译执行,就成了只有虚拟机自己知道的事情。
如今,大多数高级语言都会遵守下图中这种基于现代经典编译原理的思路,并在执行前先对程序源代码进行词法分析和语法分析处理。词法分析,语法分析以至于后面的优化器和目标代码生成器都可以独立于执行引擎,形成一个完整意义上的编译器去实现,这类代表是C/C++语言。可以选择把其中一部分步骤(如生成抽象语法树之前的步骤)实现为一个半独立的编译器,这类代表是Java语言。又或者把这些步骤和执行引擎全部集中封装在一个封闭的黑匣子之中,如大多数JavaScript的执行器。

Java语言中,Javac编译器完成了程序代码经过词法分析,语法分析到抽象语法树,再遍历语法树生成先行的字节码指令的过程。因为这部一部分动作实在Java虚拟机之外进行的,而解析器在虚拟机的内部,所以Java程序的编译就是半独立的实现。
基于栈的指令集与基于寄存器的指令集
什么是基于栈的指令集合?什么是基于寄存器的指令集?
Java编译器输出的指令流,基本上是一种基于栈的指令集架构,指令流的指令大部分都是零地址指他们依赖操作数栈进行工作。与之相对的是另一套常用的指令架构是寄存器的指令集,最典型的就是x86的二进制地址指令,说的通俗的一点就是现在我们主流PC机直接支持的指令集架构,这些命令依赖于寄存器进行工作。那么,基于栈的指令集与基于寄存器的指令集这两者之间有什么不同呢?
两种指令集各有千秋:
既然两套指令集会同时存在和发展,那肯定是各有各的优势,如果一套指令集完全优于另一套的话,就不会存在选择问题了。
基于栈的指令集的优点:
基于栈的指令集主要的优点就是可移植性,寄存器有硬件直接提供,程序直接依赖这些硬件寄存器则不可避免的要受到硬件条件的约束。如果使用栈架构的指令集,用户程序不会直接使用这些寄存器,就可以由虚拟机来实现自行决定把一些访问量较高的数据(程序计数器,栈顶缓存等)放到寄存器中以获得尽量好的性能,这样实现起来也会更加简单一些。栈架构的指令集还由一些其它的优点,如代码更加紧凑(字节码中每个字节码就对应一条指令,而多地址指令集中还需要存放参数),编译器实现更加简单。
基于栈架构的致命缺点(硬件导致):
栈架构指令集的主要缺点是执行速度相对来说或稍慢一些。所有主流物理机的指令集都是寄存器架构从侧面印证了这一点。
缺点是如何形成的?
虽然栈架构指令集的代码非常紧凑,但完全相同的功能数量一般会比寄存器架构多,因为出栈,入栈操作本身就产生了相当多的指令数量。更重要的是,栈实现在内存之中,频繁的栈访问就意味着频繁的内存访问,相对于处理器来说,内存始终是执行速度的瓶颈。尽管虚拟机可以采取栈顶缓存的手段,把最常用的操作数映射到寄存器之中避免对内存的频繁访问,但这也只能是优化措施而不能彻底解决问题。由于指令数量和内存访问的原因,所以导致了栈架构指令集的执行速度会相对较慢。
探讨Java虚拟机如何执行字节码,包括解析执行与编译执行的区别,以及基于栈的指令集与基于寄存器指令集的特点。

1111

被折叠的 条评论
为什么被折叠?



