专栏首页InvQ的专栏JVM系列(一)—— 何为JVM

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文件中的常量就可以公用,从而减少数据量的作用。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java8 dubbo 调用 Collectors.toMap代码片发生的异常(IllegalStateException: Duplicate key)

    然后,这段代码是被dubbo中的线程执行的,所以,当时只抛了一句话,并没有堆栈信息,后面的日志都没有打印。以至于线程无缘无故后面的都不执行了,线程直接挂掉。

    MickyInvQ
  • dubbo 调用报Null 空指针 ,可能并不是真正的空指针

    在消费consumer调用rpc的时候,传参明明不为空(已经打日志校验),但是,在rpc提供者的第57行代码,第一行,就报空指针。

    MickyInvQ
  • 如何实现分布式调用跟踪?

    分布式服务拆分以后,系统变得日趋复杂,业务的调用链也越来越长,如何快速定位线上故障,就需要依赖分布式调用跟踪技术。下面我们一起来看下分布式调用链相关的实现。

    MickyInvQ
  • 生物信息基础(一):SSH应用,如何优雅地登录远程服务器?

    作为一名生信工程师,每天都要跟服务器打交道,如何登录到远程服务器上去呢?通常有两种方法:

    简说基因
  • 基于YOLO的王者荣耀精彩时刻自动剪辑

    为了丰富游戏短视频内容,针对王者荣耀,需要一套自动化剪辑精彩时刻的系统,以能够快速根据主播直播内容生成精彩时刻反馈到游戏短视频社区。

    小时光
  • OffensiveNim之NetUser

    哇,最近Nim火的狠诶。周末花了点时间来学习这个东西,发现还可以,就是语法有一些恶心,小众语言的通病。然后写了个简单的小工具,用来添加用户,源码如下,windo...

    鸿鹄实验室
  • (25)打鸡儿教你Vue.js

    达达前端
  • CentOS下如何更改默认的启动方式

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011415782/article/de...

    泥豆芽儿 MT
  • 闲着无聊,把bash升了

    用户3765803
  • nebula 星云模拟器适配 xbox手柄;星云模拟器支持xbox手柄;星云模拟器xbox手柄配置;

    今天在使用nebula模拟器的时候,发现xbox one手柄不能键位映射。我猜测是因为程序比较古老,没能够识别的问题;具体解决方法,可以直接使用配置文件进行配置...

    xuyaowen

扫码关注云+社区

领取腾讯云代金券