JAVA虚拟机是什么
一般理解为以下三种:
- 抽象规范。
- 一个具体的实现。
- 一个运行中的虚拟机实例
Java虚拟机抽象规范仅仅是个概念,《The Java Virtual Machine Specificaiion》一书中详细地描述了规范内容。
规范的具体实现,可能来自多个提供商,并存在于多个平台M它或者完全用软件实现。或者以硬件和软件相结合的方式来实现,下面是目前几个主流的Java虚拟机实现。
- Java SE / Java EE:Hotspot,JRockit(Oracle);J9, JikesRVM(IBM);Zulu,Zing (Azul)
- Android / Android兼容系统:Dalvik / ART
- 研究性质的JVM::Jikes RVM,Maxine VM,Graal VM
当运行一个Java程序的同时,也就在运行了一个Java虚拟机实例。 每个Java程序都运行于某个具体的Java虚拟机实现的实例上。
组成部分:
在Java虚拟机(规范)中,一个虚拟机实例的行为是分别按照子系统、内存区、数据类型以及指令这几个术语来描述的。这些组成部分一起展示抽象的虚拟机的内部抽象体系结构。但是规范中对它们的定义并不是要强制规定Java虚拟机实现内部的体系结构,更多的是为了产格地定义这些实现的外部格征。来定义任何Java虚拟机实现都必须遵守的行为。
下图中Java虚拟机(实现)的结构框图,包括在规范中描述的主要子系统和内存区。每个Java虚拟机都有一个类装载器子系统,它根据给定的全限定名来装类型(类或接口 )。 同样每个Java虚拟机都有一个执行引擎,它负责执行那当包含在被装载类的方法中的指令。
Java虚拟机的主要任务是装载class文件并且执行其中的字码。Java 虚拟机包含一个类装载器(class loader),它可以从程序和API中装载class文件。Java API中只有程序执行时需要的那些类才会被装载。字节码由执行引擎来执行。
执行引擎:
执行引擎是Java虚拟机的核心。在Java虚拟机规范中,执行引擎的行为使用指令集来定义。对于每条指令,规范都详细规定了当实现执行到该指令时应该处理什么。但是却对如何处理言之甚少。实现的设计者行权决定如何执行字节码,实现可以采取解释,即时编译或直接用芯片上的指令执行。
操作字节码一般分为以下四种实现:
- 最简单的执行引擎就是一次性解释字节码。
- 另一种执行引擎更快,但是也更消耗内存,叫做“即时编译器:(just-in-time compiler)。在这种情况下,第一次被执行的字节码会被编译成本地机器代码。编译出的本地机器代码会被缓存(CodeCache),当方法以后被调用的时候可以重用。
- 第三种执行引擎是自适应优化器。虚拟机(运行的实例)开始的时候解释字节码,会监视运行中程序的活动。记录下使用最频繁的代码段。程序运行的时候,虚拟机只把那些活动最频繁的代码(热点代码)编译成本地代码,其他的代码继续保留为字节码,一个自适应的优化器可以使得Java虚拟机在80%-90%的时间里执行被优化过的本地代出,而只需要编译10%-20%对性有影响的代码。
- 最后一种虚拟机由硬件芯片构成。它用本地方法执行 Java字节码,这种执行引擎实际上是内嵌在芯片里的。
前三种处理方式,都是在此前实现方式下进行的优化。在虚拟机中是相辅相成的。
何为热点代码:1:被多次调用的方法 2:被多次执行的循环体
阈值如何判断:
方法计数器,统计被多次调用的方法次数,该计数器统计的并不是方法被调用的绝对次数,而是在一段时间内方法被调用的次数。server模式下默认是10000次,可以通过-XX:CompileThreshold来设置
回边计数器,统计一个方法中循环体代码执行的绝对次数,在字节码中遇到控制流向后跳转的指令称为回边,主要通过OnStackReplacePercentage设置。
指令集:方法的字节码流是由Java虚拟机的指令序列构成的,每一条指令包含一个单字节的操作码。后倒跟随0个或多个操作数。操作码表明需要执行的择作;操作数向Java虚拟机提供执行操作码需要的额外信息,操作码本身就已是规定了它是否需要跟随操作数,以及如果操作数的话,它是什么形式的。很多Java虚拟机的指令不包含操作数,仅仅是由一个操作码字节构成的,根据操作码的需要。虚拟机可能除了跟随操作码的操作数之外。还需要从另外一些存储区域得到操作数:当虚拟机执行一条指令的时候,可能使用当前常址池中的项、当前帧的局部变量中的值、或者位于当前帧操作数栈顶端的值。
运行时数据区:
Java虚拟机运行一个程序时,它需要内存来存储许多东西。例如字节码,从已装载的class文件中得到的其他信息,程序创建的对象,传递给方法的参数,返回值,局部变量,以及运算的中间结果等。Java虚拟机把这些东西都组织到几个“运行时数据区”中,以使于管理。“运行时数据区”都会以某种形式存在每一个Java虚拟机实现中。但是规范对它们的描述却是相当抽象的,这些运行时数据区结构上的细节大多数都由具体实现的设计决定。
数据类型:
Java虚拟机是通过某些数据类型来执行计算的,数据类型及其运算都是由Java虚拟机规范严格定义的。数据类型可以分为两种:基本类型和引用类型。基本类型的变量持有原始值。而引用类型的持有引用值。下图是具体的定义图:
字长的考量:
Java虚拉机中最基本的数据单元就是字(word),它的大小是由每个虚拟机实现的设计者 来决定的。字长必须足够大,至少是一个字单元就足以持有byte、short、int、char、float、 return Address或者refercncc类型的值。而两个字单元就足以持有long(64bit)或者double(64bit)类型的值,因此, 虚拟机实现的设计者至少得选择32位作为字长,或者选择更为高效的字长大小。通常根据底层主机平台的指针长度来选择字长。
类装载器子系统:
Java虚拟机有两种类装载器:启动类装载器和用户自定义类装载器。前者是Java虚拟机实现 的一部分,后者则是Java程序的一部分。由不同的类装载器装载的类将被放在虚拟机内部的不同命名空间中。
类加载的流程
一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载、验证、准备、解析、初始化、使用和卸载七个阶段,其中验证、准备、解析三个部分统称为连接。这七个阶段的发生顺序如下图所示。
加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类型的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定特性(也称为动态绑定或晚期绑定)。请注意,这里笔者写的是按部就班地“开始”,而不是按部就班地“进行”或按部就班地“完成”,强调这点是因为这些阶段通常都是互相交叉地混合进行的,会在一个阶段执行的过程中调用、激活另一个阶段。
每一个步骤的开始时机:
加载:《Java虚拟机规范》中并没有进行强制约束,这点可以交给虚拟机的具体实现来自由把握。但是对于初始化阶段,《Java虚拟机规范》则是严格规定了有且只有六种情况必须立即对类进行“初始化”(而加载、验证、准备自然需要在此之前开始):



