原文转自我自己的个人公众号:Java基础之刨根问底第1集——JVM的结构

断更好久了,终于有时间写新的了,希望可以把《刨根问底》做成一个系列。

原本第一集我是想写数据类型的,在整理原始数据类型的相关资料的时候,感觉JVM的结构是基础中的基础,如果不讲清楚JVM的结构,其他的知识也很难讲透彻,因此第一集就讲JVM的结构吧。

网上讲JVM的结构的资料还是比较多的,排除掉各种复制粘贴后,总感觉知识相对零散,总有一种雾里看花的感觉,于是我根据Oracle官方发布的《Java虚拟机规范》进行了比较全面的整理。

JVM的结构的全貌如下图所示:

注意:图中不含native stack;young和tenured的结构是默认分代的排布,不包括并行收集器和G1收集器。

下面我就图中的元素进行逐一的介绍:

从大块上来看,我将其分为三个部分:

  • PC Register:程序计数器注册机,每个线程一个。其中保存了returnAddress类型的数据,用于指向当前被执行的JVM指令的位置。

  • Stacks:栈区。每个线程创建的时候,都会在栈区为它创建一个专属的栈(Stack)。

  • Heap:堆区。所有线程共享的区域,其中的内存可以被垃圾回收器(GC)进行回收,虚拟机启动的时候就会创建。主要用于存储类的实例和数组。

下面重点介绍Stack和Heap,首先是Stack。

Stack中存储的是局部变量和部分结果,在方法的调用和返回中起到作用。每个Stack对应了一个线程,其中包含了若干个Frame(帧)。当程序执行方法的时候,会给该方法创建一个Frame,当方法调用结束,对应的Frame就会被销毁。由于一个线程统一时刻只能执行一个方法,因此一个Stack中只会有一个Frame是激活状态的(联想下Debug时候的调用栈信息可以更好的理解这一点)。

Frame主要用于存储方法内的数据、部分结果、动态连接、方法返回值和分派异常。一个Frame内包括以下两个区域:

  • LocalVariableTable:局部变量表。实际上是一个数组,用于保存局部变量的值。0号位置是this,局部变量从1开始存储。每一个boolean、byte、char、short、int、float、引用和returnAddress占用一个位置,每个long和double占用2个位置。

  • Operand Stack:操作数栈,是一个后进先出的结构。大部分JVM的指令在取操作数的时候都不会直接操作局部变量,而是从操作数栈中弹出。因为这个机制,操作数栈和局部变量表会频繁的相互交换数据。操作数栈创建时是空的,当需要数据时从局部变量或者运行时常量池中加载,运算结果会再放回操作数栈,需要的话再存入局部变量表中供后续使用。

下来再介绍下Heap。Heap区是所有线程共享的,主要存储实例和数组,虚拟机启动的时候就会创建,Heap区域的内存可以被垃圾回收。Heap内包括以下子区域:

  • Method Area:方法区。在逻辑上属于Heap,但规范中并没有强制要求,具体的JVM实现可以选择是否对该区域进行GC,也可以选择是否将该区域放在Heap中。该区域在JVM启动的时候就会创建。内部存储Run-time Constant Pool、编译后的代码、字段和方法信息等。

  • Young:年轻代。新创建的对象实例和数组存放的区域,包含Eden区和2个Survivor区。两个Survivor统一时刻只会有一个有数据(年轻代和老年代的详情我计划用另一篇介绍垃圾回收的文章进行介绍)。

  • Tenured:老年代。对象存活足够多次后就会放入老年代。

在进一步来看一下Method Area中的Run-time Constant Pool,即:运行时常量池。当每个类或接口创建的时候就会创建一个专属的运行时常量池,用来存储编译时已知的值和需要在运行时解析的方法和字段的引用。

下面用思维导图再总结一下:

下面我们用一个示例程序来看看代码中的数据都存在了哪里,也顺带介绍下JVM是怎么在字节码中通过指令集来执行我们的程序的。

程序是这样的:

大家可以先猜一下这段代码中的属性和变量都存在JVM中的哪个区中。

为了验证猜想,需要将这个类编译后的class文件反编译成Java汇编指令。JDK中提供了javap工具,可以通过“javap -v class文件名”的方式来反编译(是对class文件使用而不是对java文件)。

结果是这样的:

从图中可以看到,最上方显示了Constant pool信息,这个就是运行时常量池,它随着类的创建而被创建,所以是类中全局的。中间的DemoClass()是自动生成的构造方法,由于我们本次的目标是分析demoMethod中的数据存放位置,因此构造方法先不看。最下面就是重点要分析的demoMethod方法。可以看到,DemoClass()和demoMethod()都有自己专属的本地变量表LocalVariableTable。

首先,从最上方的运行时常量池中可以看到,我在类中定义的三个属性field、demoClass和fieldArray都存在了heap中Method Area中的运行时常量池中。在介绍运行时常量池时,介绍过这里就是存储编译时已知的值和类的类属性的引用,因此不但field、demoClass和fieldArray作为属性引用出现在常量池中,而且localArg、localVar、localClass和localArray也在常量池中。在常量池中还看到了方法和类自身的一些信息的引用。这里只是存放引用的,具体的值存放在heap的年轻代或老年代中。

下面重点看一下demoMethod方法,我从Code中的0:开始介绍。

0:iconst_0

1:istore_2

这两行指令对应了int localVar = 0;这行代码。

iconst指令用于把程序中的常量push到操作数栈中,iconst是一个家族,iconst_0是其中的一个,表示存放的操作数的值是0,这样就不用把值放到heap中用的时候再去取了,效率会高一些,也省内存。

istore指令用于从操作数栈顶弹出一个操作数存入localVariable中,下划线后面表示存入本地变量表中的位置,因此可以在最下方的本地变量列表中看到Slot为2的那一行显示了localVar。这里补充一下,可以看到Slot是1的行显示localArg,这个是方法的变量,而0号位则是this,就是我们经常在程序中用的那个this。也就是说,this永远都是本地变量表中的0号位,然后再按顺序排列参数列表和方法中的变量。

2:new   #4

5:dup

6:invokespecial   #5

9:astore_3

这四行对应了程序中的DemoClass localClass = new DemoClass();这行代码。

new指令用于创建一个对象,#后的数字是在常量池中的索引,从最上方类的常量池列表中可以看到#4对应的就是一个DemoClass的实例,实例会在新生代创建,然后对象的引用会push到操作数栈上。

dup指令用于将操作数栈顶的值拷贝一份再push到栈中,这样栈顶的2个位置都是这个值。用于后续的处理。

invokespecial指令用于调用实例的方法,#后面表示常量池中的位置。通过看常量池中的#5,这一步是调用了类的构造方法。

astore指令用于从操作数栈中弹出一个引用然后存入本地变量中,下划线后面的数字是本地变量表的位置,可以看到3号位正是localClass。

bipush 10

newarray int

astore_4

这3行对应了程序中的int[] localArray = new int[10];这一行。

bipush指令用于将一个字节push到操作数栈中,后面跟的是这一个字节的数据。一个字节能表示的整数范围是-128到127,10在这个范围内。这里的10是因为程序中声明了大小为10的数组。

newarray指令用于创建数组,后面跟的是类型。该指令会从操作数栈中弹出刚刚push进去的数组长度,用于创建数组,数组会被创建到新生代中,数组引用被push到操作数栈顶。

astore_4这个已经解释过了,将栈顶刚刚创建的数组引用存入本地变量表的第四个位置。

iload_1

iload_2

iadd

这三行对应了程序中的localArg + localVar。

iload指令用于从本地变量表中加载数据push到操作数栈中,下划线后面的数字是本地变量表的位置。也就是从本地变量表1、2两个位置向操作数栈push数据,这两个位置放的就是localArg和localVar的值。

iadd指令则是从操作数栈中弹出2个int值然后相加,将结果再push到操作数栈顶。

ireturn

这条指令对应了程序中的return,表示方法返回一个int型数据,即:弹出操作数栈顶,然后销毁该方法的操作数栈。

以上就是这段程序内各个属性和变量存放位置的解释,可能有人会得出这样一个结论:类的属性都会以引用的方式存放在运行时常量池中,方法中包括参数在内的局部变量只要是对象和数组就会存到heap中,像int就会直接存在栈中。然而,并不是所有的int都会存放到栈中

在demoMehod方法中有2个int值,一个是0,另一个是10。0被存入栈是因为有专门为零准备的iconst_0指令,10被存入的原因是因为它可以用一个字节表示,因此用了bipush指令直接压入操作数栈。可能有人想到,0也可以用一个字节表示,为什么没有用bipush呢?实际上,用bipush也可以,对于字节码来说,iconst_0指令后面不需要跟值,而bipush后面需要跟一个字节的值,因此会比前者大一个字节。

如果数据的值比一个字节大,或者是不常见的double型数据时,就不会直接存入stack中了,而是会存入heap中。java8官方jvm规范中,举了下面这个例子:

javap的结果是这样的:

可以看到,超过一个字节的1000000、0xffffffff和不是常见数字的double都使用了ldc指令,该指令会根据索引从运行时常量池中获取值,然后将得到的值push到操作数栈中。也就是说,这些数字都是存在heap中的。

到此,第1集JVM的结构就介绍完了。

更多内容请关注我的公众号:

Java基础之刨根问底第1集——JVM的结构相关推荐

  1. Java基础之刨根问底第6集——集合与List

    本系列不适合初学者,读者应具备一定的Java基础. 本系列依据Java11编写 内容简介: 从本集开始,会进入Java Collections Framework的介绍,这部分所涉及的容器对象是和我们 ...

  2. Java基础能力精选文章合集200篇

    小编根据知识图谱整理了CSDN技术大咖的优质文章200篇,帮助Java工程提升基础能力,实现系统化持续学习! Java工程师基础能力文章200篇大合集包含: [JavaSE]30篇.[Lambda表达 ...

  3. JAVA基础:JDK、JRE、JVM的概念

    java(是一门纯面向对象的)编程语言的特点: 1, 面向对象 ①, 封装 ②, 继承 ③, 多态 2, 安全性 3, 跨平台 JDK JDK是 Java 语言的软件开发工具包,主要用于移动设备.嵌入 ...

  4. Java基础知识小练习合集

    基础知识小练习合集: 1.说明基本数据类型变量之间强制类型转换的使用规则和强转可能出现的问题. (1)使用强制转换符: (2)强制类型转换,可能会导致精度损失(降低或溢出) 容量小的数据类型变量→容量 ...

  5. Java基础 课后作业错题集

    目录 ----------  ch01-ch02 ----------  ch03 ----------  ch04 ----------  ch05 ----------  ch05编程题 ---- ...

  6. Java基础:JDK、JRE、JVM的区别与联系

    1. 详细介绍 1.1 JVM – java virtual machine JVM就是我们常说的java虚拟机,它是整个java实现跨平台的 最核心的部分,所有的java程序会首先被编译为.clas ...

  7. JAVA基础之JDK、JRE、JVM关系

    什么是JRE和JDK JDK(Java Development Kit Java开发工具包) JDK是提供给Java开发人员使用的,其中包含了java的开发工具,也包括了JRE.所以安装了JDK,就不 ...

  8. Java基础学习总结(120)——JVM 参数使用详细说明

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

  9. java基础学习之this和super和内存结构

    this的内存结构 this的特性 this是什么,在内存方面是怎么样的? this是一个变量,是一个引用.this保存当前对象的内存地址,指向自身.所以,严格意义上来说,this代表的就是" ...

最新文章

  1. 百度集团副总裁吴甜:技术创新持续为产业发展注入新动能|MEET2022
  2. mac远程连接centos安装mysql_centos安装Mysql并远程连接
  3. Jackson ObjectMapper
  4. java线程之线程通信控制
  5. 方形物体绕中心旋转的扭力_三维旋转
  6. iOS15实现音乐播放器
  7. Apache Hudi 在 B 站构建实时数据湖的实践
  8. latex中括号大小控制 [转]
  9. intelj idea中除了Find Usage外的另一种查找级联调用的方法
  10. C#.Net工作笔记009---c#中Yield Return语法的作用和好处
  11. Cause: java.sql.SQLException: Could not retrieve transation read-only status server
  12. 使用纯CSS实现圣诞节雪花图案
  13. .net项目开发工具接口说明
  14. 转载——巨详细的MD5加盐,大佬详解
  15. java单例设计模式懒汉_java单例设计模式之懒汉模式
  16. 数字万用表常用软件分享:数字万用表自动计量软件数字万用表上位机软件
  17. LaTeX 各种命令,符号
  18. Windows Server 2016 (Updated Feb 2018) (x64)下载
  19. Windows安全设置-当前的安全设置不允许从该位置下载文件
  20. dsp版win10和普通版区别_图文详解win10各个版本之间有什么区别

热门文章

  1. Java 树形结构目录树的几种生成方式
  2. 解决markdown图片存储位置的问题
  3. 小程序获取code 获取 openId
  4. php下载文件并重命名,通过php下载文件并重命名
  5. layui点击弹出层中按钮,关闭弹出层
  6. 三星530换固态硬盘_imac换固态硬盘,提升运行速度
  7. 基于C++如何使用EGE做一个简单的坦克大战游戏
  8. Python 计算思维训练——字典与字符串练习(二)
  9. 一次解决你的图像尺寸和定位问题。
  10. 大数据面试题_Hive篇