JVM 基本介绍

阅读文本大概需要 5 分钟。

什么是 JVM

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

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 以前的版本基本是这样分的:

在 JDK 1.8 以后的版本大致划分如下:

此处注意一下,之所以在 JDK 1.8 以后 有了 Meta Space,其设计的目的在于规避永久代溢出的问题,因为 Meta Space 是可以自动扩容的,就跟 Java 中的集合一样。

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

总结

  • JVM 负责软件层面的机器码翻译,可以把我们写的 .java 文件翻译成机器可以识别的机器码
  • JVM 负责内存管理
  • JVM 的运行时数据区包括方法区、堆、虚拟机栈、本地方法栈和程序计数器
  • JVM 中的方法区和堆区是所有线程共享的,其他区域都是线程独享的

本文分享自微信公众号 - 程序猿杂货铺(zhoudl_l),作者:zhoudl

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-03-12

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

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 你不知道的 Java 注解那些事!

    注解对于开发人员来讲既熟悉又陌生,熟悉是因为只要你是做开发,都会用到注解(常见的 @Override),陌生是因为即使不使用注解也照常能够进行开发,注解不是必须...

    周三不加班
  • 记一次服务器被植入挖矿木马CPU飙升200%解决过程

    线上服务器用的是某讯云的,欢快的完美运行着 Tomcat,MySQL,MongoDB,ActiveMQ 等程序。突然一则噩耗从前线传来:网站不能访问了。

    周三不加班
  • Quartz原理解密

    quartz是一个用java实现的开源任务调度框架,可以用来创建简单或者复杂的任务调度,并且可以提供许多企业级的功能,比如JTA以及集群等,是当今比较流行的JA...

    周三不加班
  • JVM系列(一):Java虚拟机内存模型

    Java虚拟机,简称JVM(Java Virtual Machine),是Java语言中最为核心的一个东西,Java程序运行离不开它,因为它的存在,使得Java...

    xcbeyond
  • JVM 运行时的内存分配

      首先我们必须要知道的是 Java 是跨平台的。而它之所以跨平台就是因为 JVM 不是跨平台的。JVM 建立了 Java 程序和操作系统之间的桥梁,JVM 是...

    IT可乐
  • 【JVM系列】1.Java虚拟机内存模型

    Java虚拟机,简称JVM(Java Virtual Machine),是Java语言中最为核心的一个东西,Java程序运行离不开它,因为它的存在,使得...

    xcbeyond
  • JVM 运行时数据区详解

      Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同数据区域。

    阿豪聊干货
  • Java虚拟机基础——2JVM运行时数据区

    本篇文章主要讲解JVM运行时数据区,所以我们按照线程是否私有的维度将本篇文章一分为二,分为线程私有数据区和所有线程共有的数据区。而在线程私有的数据区又可以分为程...

    隔壁老李头
  • JVM学习笔记

      java引用类型分为四种:类、接口、数组类和泛型参数。其中泛型参数会在编译过程中被擦除。因此 Java 虚拟机实际上只有前三种。在类、接口和数组类中,数组类...

    良辰美景TT
  • 深入理解JVM的内存区域划分

    JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模...

    沁溪源

扫码关注云+社区

领取腾讯云代金券