JVM系列(一)—— 何为JVM

JVM能够跨计算机系结构来执行JAVA字节码,主要是由于JVM屏蔽了与各个计算机平台相关的软件或硬件之间的差异,使得与平台相关的耦合统一由JVM提供者来实现。

JVM的全称是Java Virtual Machine(Java虚拟机),它通过模拟一个计算机来达到一个计算机所具有的计算功能。我们先来看看一个真实的计算机如何才能具备计算的功能。

  • 指令集,这个计算机所能识别的机器语言的命令集合。
  • 计算单位,即能够识别并且控制指令执行的功能模块。
  • 寻址方式,地址的位数,最小地址和最大地址的范围,以及地址的运行规则
  • 寄存器定义,包括操作数寄存器,变址寄存器,控制寄存器等的定义,数量和使用方式。
  • 存储单元,能够存储操作数和保存操作结构的单元,如内核缓存,内存和磁盘等。
    上面几个和我们所说的代码执行最密切的还是指令集部分,下面简单说下计算机中指令集是如何定义的。

什么是指令集,有何作用

所谓指令集就是在CPU中用来计算和控制计算机体系的一套指令的集合,每一种新型的CPU在设计时都规定了一些列与其他硬件电路配合的指令系统。而指令集的现金与否也关系到CPU的性能发挥,它是CPU性能的一个重要标志。

当前计算机中有哪些指令集?
从主流的体系,分为精简指令集RISC和复杂指令集CISC。

指令集与汇编语言有什么关系?
指令集是可以直接被机器识别的机器码,也就是它必须以二进制格式存在于计算机中。
而汇编语言是能够被人识别的指令,汇编语言在顺序和逻辑上是与机器指令一一对应的。换句话说,汇编语言是为了让人更容易地记住机器指令而使用的助记符。

指令集与CPU架构有何联系?
CPU的架构会影响到指令集。

回到JVM的主题中来,JVM和实体机到底有何不同呢?

  • 一个抽象规范,这个规范就约束了JVM到底是什么,它有哪些组成部分,这些抽象的规范都在the java virtual machine specification中详细描述了
  • 一个具体的实现,所谓具体的实现就是不同的厂商按照这个抽象的规范用软件或软件和硬件结合的方式在相同或者不同的平台上的具体的实现。
  • 一个运行中的实例,当用其运行要给java程序时,他就是运行中的一个实例,么个运行中的java程序都是一个jvm实例。
    JVM和时提及一样也必须有一套合适的指令集,则个指令集能够被JVM解析执行。这个指令集我们称之为JVM字节码指令集,符合CLASS文件规范的字节码都可以被JVM执行。

JVM体系结构

除了指令集,JVM还需要一下几个部分

  • 类加载器,在JVM启动时或者在类运行时将需要的class加载到JVM中。
  • 执行引擎,执行引擎的任务是负责执行class文件中包含的字节码指令,相当于实际机器上的CPU。
  • 内存区,将内存区划分成若干个区以模拟实际机器上的存储,记录和调度功能模块,如实际机器上的各种功能的寄存器或者PC指针的记录器等。
  • 本地方法调用,调用c或者c++实现的本地方法的代码返回结果。
    这里写图片描述

类加载器

每个被JVM装在的类型都有一个对应的java.lang.Class类的实例来表示该类型,该实例可以唯一表示被JVM装载的class类,要求这个实例和其他类的实例一样都存放在java的堆中。

执行引擎

执行引擎是JVM的核心部分,执行引擎的作用就是解析JVM字节码指令,得到执行结果。在《Java虚拟机规范》中详细地定义了执行引擎遇到每条字节码指令时应该处理什么,并且应该得到什么结果。但是没有规定执行引擎应该如何或采取什么方式处理而叨叨这个结果。因为执行引擎具体采用什么方式由JVM的实现厂家自己去实现,是直接解释执行还是采用JIT技术转成本地代码区执行,还是采用寄存器这个芯片模式区执行都可以。所以,执行引擎的具体实现有很大的发挥空间。如sun的hotspot是基于栈的执行引擎,而google的dalvik是基于寄存器的执行引擎。
执行引擎也就是执行一条条代码的流程,而代码都是包含在方法体内的,所以执行引擎的本质上就是执行一个个方法所串起来的流程,对应到操作系统中一个执行流程就是一个Java进程还是一个java线程呢?很显然是后者,因为一个java进程可以有多个执行的流程。这样说来,每个java线程就是一个执行引擎的实例,那么在一个JVM实例中就会同事有多个执行引擎在工作,这些执行引擎有的在执行用户的程序,有的在执行内部的程序(如java垃圾收集器)

java内存管理

执行引擎在执行一段程序时需要存储一些东西,如操作码需要的操作数,操作码的执行结果需要保存。class类的字节码还有类的对象等信息都需要在执行引擎执行之前就准备好。
从上图看出一个JVM实例会有一个方法区,java堆,java栈,PC寄存器,和本地方法区。其中方法区和java堆是所有线程共享的,也就是可以被所有的执行引擎实例访问。每个新的执行引擎实例被创建时会为这个执行引擎创建一个java栈和一个PC寄存器,如果当前正在执行与一个java方法,那么当前的这个java栈中保存的时该线程中方法调用的状态,包括方法的参数,方法的局部变量,方法的返回值以及运算的中间结果。而PC寄存器会指向即将执行的下一条指令。
如果是本地方法调用,则存储在本地方法调用栈中或者特定实现中的某个内存区域中。

JVM工作机制

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为何选择基于栈的架构

JVM执行字节码指令是基于栈的架构,也就是所有的操作数必须先入栈。
然后根据指令中的操作码选择从栈顶弹出若干个元素进行计算后再将结果压入栈中。

原因:

  • JVM要设计成与平台无关的,而平台无关性就是要保证在没有或者很少的寄存器的机器上也要同样能正确地执行java代码。
  • 为了指令的紧凑性,因为java的字节码会在网络上传输,所以class文件的大小也是设计JVM字节码指令的一个重要因素,如在class文件中字节码除了处理两个表跳转的指令外,其他全都是字节对齐的,操作吗可以只占一个字节大小,这都是为了让编译后的class文件更佳紧凑,为了提高字节码在网络上的传输效率。sun设计了一个jar报的压缩工具pack200,它可以将多个class文件中的重复的常量池的信息进行合并,如一般在每个class文件中都含有“.java/lang/String”,那么多个class文件中的常量就可以公用,从而减少数据量的作用。

猜你喜欢

转载自blog.csdn.net/uniquewonderq/article/details/80145910