JVM能够跨计算机系结构来执行JAVA字节码,主要是由于JVM屏蔽了与各个计算机平台相关的软件或硬件之间的差异,使得与平台相关的耦合统一由JVM提供者来实现。
JVM的全称是Java Virtual Machine(Java虚拟机),它通过模拟一个计算机来达到一个计算机所具有的计算功能。我们先来看看一个真实的计算机如何才能具备计算的功能。
所谓指令集就是在CPU中用来计算和控制计算机体系的一套指令的集合,每一种新型的CPU在设计时都规定了一些列与其他硬件电路配合的指令系统。而指令集的现金与否也关系到CPU的性能发挥,它是CPU性能的一个重要标志。
当前计算机中有哪些指令集? 从主流的体系,分为精简指令集RISC和复杂指令集CISC。
指令集与汇编语言有什么关系? 指令集是可以直接被机器识别的机器码,也就是它必须以二进制格式存在于计算机中。 而汇编语言是能够被人识别的指令,汇编语言在顺序和逻辑上是与机器指令一一对应的。换句话说,汇编语言是为了让人更容易地记住机器指令而使用的助记符。
指令集与CPU架构有何联系? CPU的架构会影响到指令集。
回到JVM的主题中来,JVM和实体机到底有何不同呢?
除了指令集,JVM还需要一下几个部分
每个被JVM装在的类型都有一个对应的java.lang.Class类的实例来表示该类型,该实例可以唯一表示被JVM装载的class类,要求这个实例和其他类的实例一样都存放在java的堆中。
执行引擎是JVM的核心部分,执行引擎的作用就是解析JVM字节码指令,得到执行结果。在《Java虚拟机规范》中详细地定义了执行引擎遇到每条字节码指令时应该处理什么,并且应该得到什么结果。但是没有规定执行引擎应该如何或采取什么方式处理而叨叨这个结果。因为执行引擎具体采用什么方式由JVM的实现厂家自己去实现,是直接解释执行还是采用JIT技术转成本地代码区执行,还是采用寄存器这个芯片模式区执行都可以。所以,执行引擎的具体实现有很大的发挥空间。如sun的hotspot是基于栈的执行引擎,而google的dalvik是基于寄存器的执行引擎。 执行引擎也就是执行一条条代码的流程,而代码都是包含在方法体内的,所以执行引擎的本质上就是执行一个个方法所串起来的流程,对应到操作系统中一个执行流程就是一个Java进程还是一个java线程呢?很显然是后者,因为一个java进程可以有多个执行的流程。这样说来,每个java线程就是一个执行引擎的实例,那么在一个JVM实例中就会同事有多个执行引擎在工作,这些执行引擎有的在执行用户的程序,有的在执行内部的程序(如java垃圾收集器)
执行引擎在执行一段程序时需要存储一些东西,如操作码需要的操作数,操作码的执行结果需要保存。class类的字节码还有类的对象等信息都需要在执行引擎执行之前就准备好。 从上图看出一个JVM实例会有一个方法区,java堆,java栈,PC寄存器,和本地方法区。其中方法区和java堆是所有线程共享的,也就是可以被所有的执行引擎实例访问。每个新的执行引擎实例被创建时会为这个执行引擎创建一个java栈和一个PC寄存器,如果当前正在执行与一个java方法,那么当前的这个java栈中保存的时该线程中方法调用的状态,包括方法的参数,方法的局部变量,方法的返回值以及运算的中间结果。而PC寄存器会指向即将执行的下一条指令。 如果是本地方法调用,则存储在本地方法调用栈中或者特定实现中的某个内存区域中。
JVM是如何执行字节码命令的,即,前面所说的执行引擎是如何工作的。
先看看实体机: 只接受机器指令,其他高级语言首先必须经过编译器编译成机器指令才能被计算机正确执行。 编译器:与硬件耦合的部分就交给了编译器,不同的硬件平台通常需要的编译器也不同。 当前,不同的硬件平台的差异已经被更上一层的软件平台所代替了,这个软件平台就是操作系统,与其说不同的硬件平台还不如说操作系统之间的差异,因为现在的操作系统几乎完全屏蔽了硬件。所以,现在编译器和操作系统的关系会更佳容易让让人理解。如C语言在windows下的编译器为Microsoft C,而在linux下通常是gcc。 一个程序从编写到执行的阶段: 源代码——》预处理器——》编译器——》汇编程序——》目标代码——》链接器——》可执行程序 除了源码和最后的可执行程序,中间的所有环节都是由现代意义上的编译器统一处理的。如,在Linux环境下, 我们通常安装一个软件需要经过configure、make、make install,make clean。 configure为这个程序在当前的操作系统下选择合适的编译器来编译这个程序代码,也就是为这个程序代码选择合适的编译器和一些环境参数。 make自然就是对程序代码进行编译操作了。它会将源码编译成可执行的目标文件。 make install将已经编译好的可执行文件安装到操作系统指定或默认的安装目录下。 make clean用于删除编译时产生临时的目录或文件
值得注意的是,我们通常所说的编译器都是将某种高级语言直接编译成可执行的目标机器语言(实际上,在windows下,是需要动态链接的目标二进制文件,DLL)但是实际上,还有有一些编译器,是将一种高级语言编译成另一种高级语言,或者将低级语言编译成高级语言(反编译),或者将高级语言编译成虚拟机目标语言,如JAVA比那一起。 再说,如何让机器(不管是实体机还是虚拟机)执行代码的主题,不管是何种指令集,都只有最基本的元素,加减乘除,求余,求模等。这些运算又可以进一步分解成二进制位运算,与或非,异或等。这些运算又可以通过指令完成,而指令的核心目的就是需要运算的种类(操作吗)和运算所需要的数据(操作数),以及从哪里(寄存器或栈)获取操作数,将运算结果存放到什么地方(寄存器或栈)等。这种不同的操作方式又将指令划分为一地址指令,二地址指令,三地址指令,零地址指令等n地址指令。相应的指令集会有相应的架构实现,如基于寄存器的架构实现或基于栈的架构实现,这里的基于寄存器或者栈都是指再一个指令中的操作数是如何存取的。
JVM执行字节码指令是基于栈的架构,也就是所有的操作数必须先入栈。 然后根据指令中的操作码选择从栈顶弹出若干个元素进行计算后再将结果压入栈中。
原因: