JVM 从入门到实战--- 01 JVM 基本介绍

什么是 JVM

先来看下百度百科的解释:java

JVM 是 Java Virtual Machine(Java 虚拟机)的缩写,JVM 是一种用于计算设备的规范,它是一个虚构出来的计算机,是经过在实际的计算机上仿真模拟各类计算机功能来实现的。算法

晦涩难懂有没有,简单理解就是说虚拟机是物理机的软件实现。数组

Java 的设计理念是 WORA(Write Once Run Anywhere,一次编写处处运行)。编译器将 Java 文件编译为 Java .class 文件,而后将 .class 文件输入到 JVM 中,JVM 执行类文件的加载和执行,最后转变成机器能够识别的机器码进行最终的操做。多线程

为何要学习 JVM

每一个 Java 开发人员都知道字节码经由 JRE(Java 运行时环境)执行。但他们或许不知道 JRE 实际上是由 Java 虚拟机(JVM)实现,JVM 分析字节码,解释并执行它。做为开发人员,了解 JVM的 架构是很是重要的,由于它使咱们可以编写出更高效的代码。架构

可是 JVM 在帮咱们实现 Write Once Run Anywhere 的同时,有利有弊,由于在这个过程当中涉及到了内存管理,尤为是多线程状况下的内存管理问题,因此咱们更应该学习 JVM 的知识来帮助本身写出更好的代码。学习

根据上边对 JVM 的概念介绍咱们知道,JVM 的主要做用在于如下两方面,以后咱们的介绍也会以此着手。优化

  • 软件层面的机器码翻译线程

  • 内存管理翻译

最近也在学习《深刻理解 Java 虚拟机》这本书,此处贴个书中的图过来:设计

下边就详细介绍一下这张图中的各个组件

运行时数据区

这个区域描述的是 Java 代码运行时的状态,是咱们很是关注的一个状态-程序运行状态,由于咱们写代码就是为了运行,不运行的状态对咱们是没什么吸引力的。说白了 Java 代码不外乎 数据 指令 控制 这三类型语句,因此咱们将 JVM 运行时数据区能够划分为以下两大类:

  • 数据

    • 方法区

    • 堆(Heap)

  • 指令

    • 虚拟机栈

    • 本地方法栈

    • 程序计数器

程序计数器

定义:指向当前线程正在执行的字节码指令的地址 也就是行号

注意:咱们须要思考一个问题,个人当前线程自己已经在执行了,为何还要找个寄存器把他的执行行号记录下来呢?

由于咱们程序执行的最小单位是线程,而线程在 CPU 上执行的时候是抢占式的,这样的话就存在线程被挂起的状况,例如:有 A B 两个线程,若是 A 线程执行过程被 B 线程抢占了 CPU,则须要把挂起的 A 线程 当前执行到的行号存储下来,等到 A 从新得到 CPU 时间片执行权的时候去程序计数器得到上一次执行的行号以便于继续执行这个程序。

因此,每一个线程都有本身的 程序计数器,并且是互不干扰的,属于线程私有区域

  • 若是执行的是一个 Java 方法,计数器记录的是正在执行的虚拟机字节码指令的地址

  • 若是执行的是一个 Native 方法,计数器的值则为空(undefined)

虚拟机栈

定义:存储当前线程运行方法所须要的数据、指令和返回地址,生命周期与线程相同,一样属于线程私有区域

每一个 Java 方法在执行的同时都会建立一个栈帧用于存储局部变量、操做数栈、方法出口等信息,

以下所示,这个栈帧会存储的信息包括:

  • 局部变量表

  • 操做数栈

  • 动态连接

  • 出口

  • ... ...

每个方法从调用直至执行完成的过程,其实真正对应的是一个栈帧在虚拟机栈中入栈到出栈的过程。

其中局部变量表存放了编译器可知的各类基本数据类型、引用对象等。须要注意的是由于局部变量表空间长度只有 32 位,若是是 long 和 double 类型的话会占用 2 个局部变量表空间,其余数据类型只占用 1 个。

注意:局部变量表所需的内存空间在编译期间就会车队分配完成,由于在进入一个方法时,这个方法须要在栈帧中分配多大的局部空间是彻底肯定的,方法运行期间局部变量大小是不会改变的。

本地方法栈

和虚拟机栈相似,只不过他存储的是当前线程调用的本地方法所须要的数据、指令和返回地址等,本地方法时标识有 Native 关键字的方法,此处就不展开描述了,参考上述虚拟机栈的介绍。

另外,根据《深刻理解 Java 虚拟机》这本书的介绍,有些虚拟机(如 Sun HotSpot 虚拟机)直接就把本地方法栈和虚拟机栈合二为一了。

方法区

这块区域属于线程共享群与,主要存储的信息包括已被虚拟机你加载的类信息(类的元信息)、常量、静态变量、JIT(编译器编译后的代码)等数据。

方法区有一块区域咱们称之为 运行时常量池,存放编译期生成的各类字面量和符号引用,运行时常量池有一个重要特征是具有动态性,也就是说在运行期间依然能够将新的常量放入池中,咱们开发经常使用的有 String 类的 intern() 方法

堆(Heap)

属于线程共享区域,在虚拟机启动时建立,是虚拟机管理的内存中最大的一块。它的惟一做用就是存放对象实例。

根据虚拟机规范的描述是:全部的对象实例及数组都要在堆上分配。固然随着如今技术的发展优化这个也变得没有那么绝对,后续会进行分享。

这块区域也是垃圾收集器管理的主要区域,现现在流行的垃圾回收器基本都采用的是分代收集算法,因此也就衍生了一些分代方式,

好比对于内存模型的划分,在 JDK1.8 之前的版本基本是这样的:

  • 新生代

    • Eden

    • s0

    • s1

  • 老年代

  • 永久代

在 JDK 1.8 之后的版本:

  • 新生代

  • 老年代

  • Meta Space

此处小提一下,之因此在 JDK 1.8 之后 有了 Meta Space,其设计的目的在于规避永久代溢出的问题,由于 Meta Space 是能够自动扩容的,就跟 Java 中的集合同样。

以上种种的划分方式,都是为了更好地回收内存或者分配内存,从下一篇开始就开始学习内存分配及垃圾回收相关算法啦!

总结

  • JVM 负责软件层面的机器码翻译,能够把咱们写的 .java 文件翻译成机器能够识别的机器码

  • JVM 负责内存管理

  • JVM 的运行时数据区包括方法区、堆、虚拟机栈、本地方法栈和程序计数器

  • JVM 中的方法区和堆区是全部线程共享的,其余区域都是线程独享的

相关文章
相关标签/搜索