目录
1. 引言
2. 简介
3. 冯诺依曼体系
4. CPU组成和工作流程
5. JVM组成和工作流程
6. 总结
7. 思考
1. 引言
学习java这门语言,最先学到的除了一些基础的语法知识,还有关于jvm的一些东西,像堆栈、方法区、垃圾回收,站在巨人的肩膀上,我们只需要对jvm有个大概的了解就行了,可是仅靠书本和网络上一些雷同的jvm资料,很难对jvm有个全面的认识,为了不仅仅只是记住这些概念性的内容,本篇文章将从冯诺依曼体系的角度重识jvm。
java语言能够取得巨大成功,很大一部分要归功于 “Write once, run everywhere”,而JVM正是提供这种能力的关键。JVM全称是JAVA Virtual Machine,中文翻译为JAVA虚拟机,它就像一台真实机器运行在多个平台上,如Windows,linux,Mac OS等,开发者不论在哪个平台上编写java代码并编译成class文件,都能够被这些平台上部署的JVM加载并执行。一般情况下,java开发人员不需要关注特定平台(操作系统),因为JVM屏蔽了不同平台的差异性,为java应用提供了统一的运行环境,jvm扮演了非常重要的中介者角色,所以必须要了解JVM,JVM的运行机制直接影响着java代码的执行效果和性能,如果完全不了解JVM组成和原理,也就无法编写稳定和高效的代码。
JVM有多个实现版本,如SUN公司(2009年被Oracle收购)的Hotspot,BEA公司(2008年被Oracle收购)的Jrockit,IBM的J9,这些版本有很多差异(主要体现在垃圾回收,JIT,AOT等),但是它们都基本遵循JVM定义,所以同一份代码不需要做任何修改在这些JVM执行的效果都是一样的。按照JVM定义,JVM由执行引擎、内存空间(线程共享空间如堆、方法区,以及线程独享的栈空间)、PC(程序计数器)。我们知道构成一台现代计算机需要有CPU,内存,和IO设备,遵循冯诺依曼体系,如果我们试着也将jvm按照冯诺依曼体系划分,得出了下面这张参照表:
冯诺依曼体系 | JVM | 现代计算机 |
计算器 | 执行引擎 | ALU + DR(CPU的一部分) |
控制器 | PC | PC + IR + 控制器组(CPU的一部分) |
内存 | 堆、栈、方法区 | 内存(RAM) |
通过上表对比,JVM也符合冯诺依曼体系,但又和现代计算机稍有不同,JVM字节码指令是基于栈的,而现代计算机的cpu汇编指令是基于寄存器的,为了更好理解这些共同点和差异性,就无法绕开冯诺依曼体系。
在冯诺依曼之前,图灵机提出了计算机的理论模型,简单地说,图灵机描述了一个使用机器解决数学计算的过程:给定一个输入,机器经过多次机器头移动、擦除、写入等操作之后,最终给定一个确定的结果,这个计算过程引出了程序指令的概念,即一条指令由一连串机器动作组成。符合图灵机的计算机需要有输入、输出和程序指令,图灵机的提出,实现了计算机从0到1的突破,但是也有很大的局限性,比如程序指令无法存储,只能完成单一任务等。早期的图灵机如ENIAC最早是为了计算导弹弹道而设计的,后来可以编程计算其他问题,但是此处的编程不是我们熟悉的编写代码,而是通过改变插线改变计算过程,可以看下下图:
上图ENIAC复杂的排线让人看着就头晕,ENIAC实现了图灵机,但单单实现了图灵机的计算机只能应付具体问题的计算,要想改变用途,也只能重新设计电路或者改变排线。正是在研发ENIAC过程中,参与者冯诺依曼看到了这个缺点,于是提出了著名草案:《First draft of a Report on the EDVAC》。
该草案提出了以下重要的概念:
1)计算器
2)控制器
3)内存
4)I/O
之前的计算机的程序都是固定用途的,无法更改或者很难更改(重新设计电路或者插线),为了让计算机更加通用,冯诺依曼提出了“stored-program computer”, 中文翻译为存储程序计算机,存储程序计算机要同时满足两个条件,1.可编程;2.程序可以被存储。常见的普通计算器将加减乘除算法固化到电路板中,你只能拿它来算加减乘除,干不了其它事,所以普通计算器是不可编程的,不是通用计算机;上图的ENIAC可以通过排线编程改变用途,但是无法保存之前的排线,每次只能重新排线,ENIAC的程序不能被存储,所以ENIAC不是通用计算机。
冯诺依曼认为程序应该被当成普通数据一样存储,进而提出了通用计算机理论体系:以计算器为中心,内存存储程序数据和普通数据,控制器控制指令的顺序读取、解析和普通数据的流向,计算器接到控制器发送的执行信号,获取指令和数据后执行指令,并将处理完成的数据写入到内存中,因为要和人交互,还需要I/O设备接收输入数据和打印结果,如下图:
冯诺依曼体系构建了通用计算机的基本结构,在冯诺依曼体系指导下,现代计算机由CPU、内存、IO组成,但是毕竟冯诺依曼体系是理论层面的,为了工程实现,现代计算机做了很多改进:如CPU同时充当了计算器和控制器两个角色;为了实现组件模块化,CPU与内存和I/O并不直接打交道,它们通过总线连接,大大加强了扩展性,可以随意更换其他不同容量的内存条或不同品牌的CPU。如下图:
上图是现代通用计算机主要组成部分,有CPU、存储器(内存),输入/输出(I/O)构成,CPU是核心部件,CPU既是计算器也是控制器,计算器由ALU+DR(逻辑计算单元,作数学运算和逻辑运算,DR数据寄存器缓存内存中的指令数据和普通数据)组成,控制器由PC+IR+控制器组(PC计算指令移动和顺序获取指令,IR寄存器解析指令,控制器作时钟等同步操作)组成。cpu中大量使用了寄存器,主要作用是加快cpu工作速度,寄存器可以存储指令、数据、地址,如DR数据寄存器缓存了内存中的数据,IR指令寄存器存储了cpu指令,其他部分寄存器会被用来存储地址,因为cpu指令无法在运行之前就确定所有数据和指令的绝对地址,所以需要在程序运行时用寄存器保存地址并寻址,比如一段子程序A,A会被其他父程序调用,A执行完成之后返回父程序继续执行,cpu指令需要知道父程序当时调用A的地址作为A执行完成之后的返回地址,在程序调用之前很难确定所有父程序的返回地址,所以在程序运行时,当父程序调用A时,将返回地址暂存到寄存器中,A执行完成后,取寄存器中的地址作为返回地址。
CPU执行流程,分为5部分:
取指令 --> 指令译码 --> 执行指令 --> 访问内存取数 --> 结果写回寄存器或内存,详情请点击:CPU的工作过程。
JVM屏蔽了平台(操作系统)的差异性,充当了计算机的角色,按照冯诺依曼体系划分,JVM由执行引擎(计算器),PC(控制器),堆栈、方法区(内存)组成,只有I/O是不需要的,虽然整体结构和现代通用计算机是一样的,但是cpu的指令是基于寄存器的,而jvm的字节码指令是基于栈的,使用栈不用记录函数返回和跳转地址,只需要入栈和出栈。JVM结构图如下:
JVM执行字节码指令流程:
JVM启动后,将编译后的class文件加载到方法区,获取合格的字节码指令,运行时,首先为新线程分配独享的栈和PC寄存器,然后执行引擎获取在方法区加载的字节码指令,顺序解释执行单条指令(或者使用JIT编译后平台的机器指令直接执行),PC寄存器记录下条指令位置,执行引擎执行完一条指令之后,读取PC寄存器的下条指令位置,顺序执行所有指令,执行指令过程中,以方法为单位生成栈帧入栈,栈帧主要存储方法的局部变量、指令的操作数栈等,当一个方法执行完成,该方法的栈帧出栈,对象的创建在堆中分配空间,栈中只保存对堆中对象的指针(引用),线程中的所有方法执行完成后,栈帧全部出栈,栈和PC寄存器也会被销毁。
大部分java开发者掌握的计算机底层知识比较匮乏,抛开背景知识直接去看jvm,很难加深对jvm的理解。本文从冯诺依曼体系比较了jvm和通用计算机,它们结构基本相同,又有所区别,比如jvm一直被吐槽运行速度慢,有一部分原因是因为jvm指令是基与栈的,基于栈的指令完成复杂功能需要多次出栈和入栈,而基于寄存器的指令,直接操作多个寄存器执行运算,运行速度会比栈快,但寄存器在不用硬件平台可能会不同,而栈的移植性高于寄存器,jvm为了多个平台的兼容,妥协的使用了栈指令。了解了jvm设计的相关背景知识,会对jvm有更好的认识。
1). 冯诺依曼体系有什么瓶颈,下一代计算机体系会是什么样的?
2). 冯诺依曼体系和摩尔定律有什么关系?