JVM体系结构与工作方式———爪哇岛探险(2)

JVM体系结构与工作方式

JAVA能够跨越计算机硬件组成差异和操作系统的差异在不同的主机上运行,主要就是JVM屏蔽了各个主机之间硬件和软件的差异,使得Java与平台的耦合性交给JVM来解决。爪哇岛的第二站就是从宏观角度上介绍JVM的体系结构和工作方式。
目录

JVM体系结构

JVM是个什么东西?

JVM的全称是Java Viutual Machine,也就是Java虚拟机,它运行在一个实际的计算机上并能仿真模拟出各种计算机的功能。我们先来看一下一个真实的计算机如何才能具备计算的功能,从以计算为中心的计算机体系结构来看,他应该具有以下几部分:

  • 指令集:这个计算机所能识别的机器语言的命令集合。
  • 计算单元:能够识别并且控制指令执行的功能模块。
  • 寻址方式:地址的位数,最小地址和最大地址范围,以及地址的运行规则。
  • 寄存器定义:对操作数寄存器、变址寄存器、控制寄存器等的定义、数量和使用方式。
  • 存储单元:能够存储操作数和保存操作结构的单元,如内核级缓存、内存和磁盘等等。
    上述五个部分中与代码相关程度最大的还是指令集部分,什么是指令集?他又有什么作用呢?指令集是在CPU中用来计算和控制计算机系统的一套指令的集合,它是体现CPU性能的一个重要标志。那指令集与汇编语言又有什么关系?指令集是可以直接被机器识别的机器码,它必须以二进制格式存储在计算机当中,而汇编语言是能够被人所识别的指令,它在逻辑和顺序上是与机器指令一一对应的。
    每个运行的Java程序都是一个JVM实例,JVM和实体机一样也有一套合适的指令集,这个指令集能够被JVM解析执行,这个指令集我们称为JVM字节码指令集。符合JVM规范的class文件字节码都可以被JVM执行。

Java代码执行流程图

JVM体系机构详解

除了指令集之外,JVM还需要以下四个组成部分:

  • 类加载器:在JVM启动时或者在类运行时将需要的class加载到JVM中并解析成JVM统一使用的对象格式。
  • 执行引擎:执行引擎的任务是负责执行class文件中包含的字节码指令,相当于CPU。
  • 内存区:将内存划分为若干个区以模拟实际机器上的存储、记录和调度功能模块。例如方法区,堆区,栈区和常量池等。
  • 本地方法调用:调用C或C++实现的本地方法的代码返回结果。
类加载器

类加载器作为JVM的对外接口,具有三个主要的功能:1.将class文件加载到JVM当中;2.将Class字节码重新解析成JVM统一要求的对象格式;3.审查每个类应该由谁来加载(是一种父优先的等级加载机制)。

执行引擎

执行引擎是JVM的核心部分,执行引擎的作用就是解析JVM字节码指令,得到执行结果。执行引擎也就是执行一条条代码的一个流程,每个Java线程都是一个执行引擎的实例。执行引擎一般有两种执行方式:
直接解释执行:解释器会将代码一句一句的进行解释,没解释一句就运行一句,在这个过程中不会产生机器码文件。
JIT(即时编译器)执行:先编译再执行,需要编译器先将字节码编译成机器码,然后再进行执行,该过程会产生机器码文件.
在实际JVM当中会对代码进行分类判断,对于热点代码(多次调用的代码和多次执行的循环体)做编译,非热点代码做解释执行。

Java内存管理

JVM的执行引擎在执行一段程序时会存储一些东西,比如操作码需要的操作数,操作码的执行结果等等。具体JVM的内存区域管理和划分会在下一篇文章进行解释!

JVM工作方式

前面简单分析了JVM的基本结构,下面再简单分析一下JVM是如何执行字节码命令的,也就是前面介绍的执行引擎是如何工作的。

机器如何执行代码

我们知道机器语言一般都是和硬件平台息息相关的,而高级语言一般都是屏蔽了所有的硬件平台和软件平台(例如操作系统)。之所以能够屏蔽就是有了编译器,可以将高级语言转化为CPU可以直接执行的机器码。通常一个程序从编写到执行会经历以下一些阶段:
源代码——>预处理器——>编译器——>汇编程序——>目标代码——>链接器——>可执行程序
除了源代码和最后的可执行程序,中间的所有环节都是由现代意义上的编译器统一完成的。

(值得注意的是,我们通常所说的编译器都是将某种高级语言直接编译成可执行的目标机器语言。但是实际上还有一些编译器是将一种高级语言编译成另外一种高级语言,或者将低级语言编译成高级语言(反编译),或者将高级语言编译成虚拟机目标语言,如Java编译器等。)

再回到如何让机器(不论是虚拟机还是实体机)执行代码的主题,不论执行什么代码都可以进一步分解为二进制的位运算,这些运算的核心目的就是确定需要运算的种类(操作码)和运算的数据(操作数),以及从哪里获取操作数(寄存器或者栈)和将运算结果存放到什么地方(寄存器或者栈)。因此基于两种不同的存放方式(寄存器和栈),指令集会有基于寄存器的架构实现和基于栈的架构实现两种方式。JVM选择的是基于栈的架构。

JVM为何选择基于栈的架构

JVM执行字节码指令是基于栈的架构,也就是所有的操作数都必须先入栈,然后再根据指令中的操作码选择从栈顶弹出若干个元素进行计算之后再压入栈中。这就和一般的基于寄存器的操作有所不同,一个操作需要频繁的入栈和出栈,如果是基于寄存器(CPU的存储器,读取数据速度较快)的话不需要这么多的移动数据的操作,那么为什么JVM仍然选择基于栈的架构呢?
主要的原因就是JVM要设计成平台无关的,而平台无关性就是要保证再没有或者很少的寄存器的机器上也要同样能正确执行Java代码,而基于寄存器的架构过于依赖于寄存器本身的硬件特性,虽然基于寄存器可能会提高性能,但很大程度上牺牲了跨平台的移植性,很难设计出一款通用的基于寄存器的指令,综上所述,JVM最终选择了基于栈的架构!

执行引擎的架构设计

了解了Java以栈为架构的原因之后,再详细看下JVM是如何设计Java的执行部件的。
见下图:

Java执行部件

每当创建了一个新的线程是,JVM会为这个线程创建一个Java栈,同时会为这个线程分配一个PC寄存器,并且此时这个PC寄存器会指向这个线程的第一行可执行代码(随着线程的执行,PC寄存器保持指向当前正在执行的代码)。每当线程调用一个新方法时,会在这个栈上创建一个新的栈帧数据结构,这个栈帧会保留这个方法的一些元信息,如方法中定义的局部变量,正常方法返回以及异常处理机制等。JVM在调用某些指令时可能需要调用到常量池中的一些常量,Java对象和构造函数等都存储在所有线程共享的方法区和Java堆中。

执行引擎的执行过程

以一个方法的执行过程解释执行引擎字节码的指令流程:

public class Math{
    public static void main(String args[])
    {
        int a=1;
        int b=2;
        int c=(a+b)*10;
    }
}

其中main的字节码指令如下:

偏移量    指令                      说明
0:      iconst_1                 常数1入栈
1:      istore_1                 将栈顶元素移入本地变量1存储
2:      iconst_2                 常数2入栈
3:      istore_2                 将栈顶元素移入本地变量2存储
4:      iload_1                  本地变量1入栈
5:      iload_2                  本地变量2入栈
6:      iadd                     弹出栈顶两个元素相加之后将结果再入栈
7:      bipush 10                将10压入栈 
8:      imul                     弹出栈顶两个元素相乘之后再将结果入栈
9:      istore_3                 栈顶元素移入本地变量3存储
10:     return                   返回

第十条指令执行完,当前的这个方法对应的这些部件会被JVM回收,局部变量区的所有值将全部释放,PC寄存器会被销毁,再Java栈中与这个方法对应的栈帧将消失。

参考资料:
参考链接博客最下方
许令波——深入分析Java Web技术内幕(第七章)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值