乐趣区

关于java:JVM笔记-JVM的发展以及基于栈的指令集架构

    1. 2011 年,JDK7公布,1.7u4 中,开始启用新的垃圾回收器G1(然而不是默认)。
    1. 2017 年,公布 JDK9G1 成为默认 GC,代替CMS。(个别公司应用jdk8 的时候,会通过参数,指定 GCG1)
    1. 2018 年,公布JDK11,带来了革命性ZGC,性能比拟强。

虚拟机介绍

虚拟机,就是虚构的计算机,能够执行一系列虚构计算机指令,大体上能够分为零碎虚拟机和程序虚拟机。它们运行时,都会受到虚拟机提供的资源的限度。

  • 零碎虚拟机:仿真模拟系统的,比方Visual BoxVMware
  • 程序虚拟机:为执行单个计算机程序设计的,比方 Java 虚拟机。

JAVA 虚拟机

Java虚拟机是一台执行字节码的虚拟机计算机,然而字节码不肯定是由 Java 语言编译而成。然而只有应用这一套虚拟机规定的语言,就能够享受到跨平台,垃圾收集以及牢靠的即时编译器。JVM和硬件之间没有间接的交互。

  • 一次编译,到处运行。
  • 主动内存治理
  • 主动垃圾回收

上面是 ava 平台文档中 Java 概念图的形容,能够看出 javac 命令在 JDK 中,也就是将 .java 文件编译成为 .class 文件,这个就是前端编译器,将源文件编译成为字节码。这个编译器不在 JRE 中,也阐明了 JRE 不包含编译环境。

JRE 和 JDK 都包含了 JVM 虚拟机。JRE 是运行时环境,而 JDK 蕴含了开发环境。

JDK7 中 java 家族的构造组成 : https://docs.oracle.com/javas…

JDK7 中 java 家族的构造组成 : https://docs.oracle.com/javas…

JVM 构造

下面的图次要包含三局部:类加载器,运行时数据区,执行引擎。

类加载器,次要是将 Class 文件(曾经通过前端编译器编译后的字节码文件),加载到运行时数据区,生成 Class 对象,这个过程会设计加载,链接,初始化等过程。

运行时区域次要分为:

  • 线程公有(每个线程有一份):

    • 程序计数器:Program Count Register, 线程公有,没有垃圾回收
    • 虚拟机栈:VM Stack,线程公有,没有垃圾回收
    • 本地办法栈:Native Method Stack, 线程公有,没有垃圾回收
  • 线程共享:

    • 办法区:Method Area,以 HotSpot 为例,JDK1.8后元空间取代办法区,有垃圾回收。
    • 堆:Heap,垃圾回收最重要的中央。

执行引擎次要包含解释器和即时编译器(热点代码提前编译好,这是后端编译器),垃圾回收器。字节码文件不能间接被机器辨认,所以须要执行引擎来做转换。

Java 代码执行流程

Java 代码变成字节码文件的过程中,其实蕴含了词法剖析,语法分析,语法树,语义剖析等一系列操作。

在执行引擎中,有 JIT 编译器,也就是第二次编译的过程会产生在这里,会将热点代码编译成为机器指令,是依照办法的维度,缓存起来(放在办法区), 也称之为CodeCache

JVM 架构模型

Java 编译器次要是基于栈的指令集架构,集体感觉次要起因是可移植性决定的,JVM 须要跨平台。指令集架构次要有两种:

  • 基于栈的指令集架构:一个办法相当于一个入栈的操作,执行完相当于出栈操作。
  • 基于寄存器的指令集架构

基于栈的指令集架构的特点

次要特点:

  • 设计实现简略,实用于资源受限的零碎,比方机顶盒,小玩具上。
  • 避开寄存器调配难题:应用零地址指令形式调配。
  • 指令流中大部分都是零地址指令,执行过程依赖操作栈,指令集更小(零地址),编译器容易实现。
  • 不须要硬件反对,可移植性强,容易实现跨平台。

基于寄存器架构的特点

  • 典型利用是 x86 的二进制指令集
  • 依赖于硬件,可移植性差
  • 性能好,执行效率高
  • 更少指令执行一项操作
  • 大部分状况下,寄存器的架构,一,二,三地址指令为主,而基于栈的指令集却是以零地址指令为主。

阐明:什么叫零地址指令,一地址指令,二地址指令?
零地址指令只有操作码, 没有操作数。这种指令有两种状况: 一是无需操作数, 另一种是操作数为默认的(隐含的), 默认为操作数在寄存器中, 指令可间接拜访寄存器。

  • 三地址指令:个别地址域中 A1、A2 别离确定第一、第二操作数地址,A3 确定后果地址。下一条指令的地址通常由程序计数器按程序给出。
  • 二地址指令:地址域中 A1 确定第一操作数地址,A2 同时确定第二操作数地址和后果地址。
  • 单地址指令:地址域中 A 确定第一操作数地址。固定应用某个寄存器寄存第二操作数和操作后果。因此在指令中隐含了它们的地址。
  • 零地址指令:在堆栈型计算机中,操作数个别寄存在下推堆栈顶的两个单元中, 后果又放入栈顶, 地址均被隐含,因此大多数指令只有操作码而没有地址域。

栈数据结构,个别只有入栈和出栈,所以操作的中央只有栈顶元素,所以地位是确定的,不须要地址。

例子
执行 2 + 3 的操作,如果是基于栈的计算流程:

iconst_2 // 常量 2 入栈
istore_1 
iconst_3 // 常量 3 入栈
istore_2
iload_1
iload_2
iadd  // 常量 2,3 出栈,执行相加
istore_0  // 后果 5 入栈

基于寄存器的计算流程:

mov eax,2   // 将 eax 寄存器的值设置为 2
add eax,3   // 将 eax 寄存器的值加 3 

从下面的例子能够看进去,基于栈的寄存器的指令更小,然而基于寄存器的指令更少。

咱们能够通过一个简略程序看一下:

public class StackStructTest {public static void main(String[] args) {int i = 2 + 3;}
}

编译后,切换到 class 目录下,应用命令反编译:

java -v StackStructTest.class

看到字节码的模块,能够看到后面有 iconst_5,其实5 就是 2+3 的后果, 也就是编译期间就会间接把 2+3 变成 5,不会在运行的时候才去计算,这个是因为23都是常量。

这个景象称之为 编译期的常量折叠

然而如果咱们把代码成上面这种状况呢?

        int i = 2;
        int j = 3;
        int k = i + j;

反编译进去的指令:

const意思是 constant(常量),storestoreage寄存器。

 stack=2, locals=4, args_size=1
         0: iconst_2  // 2 是个常量
         1: istore_1  // 2 加载到 1 号操作数栈
         2: iconst_3  // 3 是一个产量
         3: istore_2  // 3 加载到 2 号操作数栈
         4: iload_1   // 将 1 号操作数栈取出,加载进来
         5: iload_2   // 将 2 号操作数栈取出,加载进来
         6: iadd      // 两者相加
         7: istore_3  // 后果存储到索引为 3 号操作数栈中
         8: return

也就是栈架构的JVM,须要 8 条指令能力实现下面的变量相加计算。

栈架构总结

因为跨平台个性,Java 指令基于栈来设计,因为不同的 CPU 架构不同,长处是跨平台,指令集小,编译器容易实现。毛病是性能降落,实现同样性能须要更多指令。

【作者简介】
秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使迟缓,驰而不息。集体写作方向:Java 源码解析,JDBC,Mybatis,Spring,redis,分布式,剑指 Offer,LeetCode 等,认真写好每一篇文章,不喜爱题目党,不喜爱花里胡哨,大多写系列文章,不能保障我写的都完全正确,然而我保障所写的均通过实际或者查找材料。脱漏或者谬误之处,还望斧正。

2020 年我写了什么?

开源编程笔记

素日工夫贵重,只能应用早晨以及周末工夫学习写作,关注我,咱们一起成长吧~

退出移动版