JVM运行时数据区详解(13张图助你理解运行时数据区)

1. 前言

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。

 下图展示了Java虚拟机所管理的内存包括的运行时数据区域:
在这里插入图片描述

图1




 运行时数据区中各部分区域关联如下图(萌新刚看到这个图可能会蒙,不用急,这些后面会介绍到):
在这里插入图片描述

图2

2. 程序计数器(PC Register)

2.1 什么是程序计数器?

  • 程序计数器仅占用较小的内存空间(理解成一个存放数字的变量)。
  • JVM中的程序计数器是对物理PC寄存器的一种抽象模拟。
  • 程序计数器用于指示当前线程所执行的字节码的行号指示器。
  • 程序计数器在各个线程之间隔离,各个线程不共享程序计数器程序计数器线程之间不共享是为了线程切换后能恢复到正确的执行位置)。

2.2 程序计数器的作用?

 程序计数器用于存储指向下一条指令的地址。由执行引擎读取下一条指令。

在这里插入图片描述

图3

3. Java虚拟机栈(Java Stack)

3.1 什么是Java虚拟机栈?

Java虚拟机栈,早期也叫Java栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个栈帧(Stack Frame),对应着一次次的Java方法调用(一个栈帧对应一个方法)。

  • Java虚拟机栈的生命周期与线程一致。
  • 栈是运行时的单位,而堆是存储的单位

3.2 Java虚拟机栈中存储什么?

  • 每个线程都有自己的栈,栈中数据都是以栈帧(Stack Frame)的格式存在
  • 在这个线程上正在执行的每个方法都各自对应一个栈帧(Stack Frame)。
  • 栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息。

3.3 Java虚拟机栈的作用?

Java虚拟机栈主管Java程序的运行,它保存方法的局部变量(8种基本数据类型、对象的引用地址)、部分结果,并参与方法的调用和返回。

3.4 栈的特点(优点)?

  • 栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。
  • JVM直接对Java栈的操作只有两个:
     1.每个方法执行,伴随着进栈。
     2.执行结束后的出栈工作
  • 对栈来说不存在垃圾回收问题。

 对Java栈的操作如下图:
在这里插入图片描述

图4

3.5 栈帧的内部结构

 每个栈帧中存储着:

  • 局部变量表(Local Variables)
  • 操作数栈(Operand Stack)(或表达式栈)
  • 动态链接(Dynamic Linking)(或指向运行时常量池的方法引用)
  • 方法返回地址(Return Address)(或方法正常退出或者异常退出的定义)
  • 一些附加信息

 下图为栈帧内部结构图:
在这里插入图片描述

图5

3.5.1 局部变量表

 局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型)和returnAddress类型(指向了一条字节码指令的地址),局部变量表中存储数据的最小单位为变量槽(Slot)。

注意:64位长度的long和double类型的数据会占用2个变量槽(Slot),其余的数据类型只占用1个Slot。

在这里插入图片描述

图6

局部变量表所需的容量大小是在编译器确定下来的,并保存在方法的Code属性的maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小的。

3.5.2 操作数栈

 每一个独立的栈帧中除了包含局部变量表以外,还包含一个后进先出的操作数栈,也可以称之为表达式栈

 操作数栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)/出栈(pop)。

 举个例子,整数的加法的字节码指令iadd在运行的时候操作数栈中最接近栈顶的两个元素已经存入了两个int型的数据,当执行这个指令时,会将这两个int值出栈并相加,然后将相加的结果入栈

 案例演示如下图所示:
在这里插入图片描述

图7

3.5.3 动态链接

 每个栈帧都包含一个指向运行时常量池该栈帧所属方法的引用。包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接

 在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用保存在class文件的常量池里。比如,描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用在运行期间转换为调用方法的直接引用

3.5.4 方法返回地址

方法返回地址存放了调用该方法的程序计数器的值

 一个方法的结束,有两种方式:

  • 正常执行完成。
  • 出现未处理的异常,非正常退出。

 无论通过哪种方式退出,在方法退出后都返回到该方法被调用的位置。方法正常退出时,调用者的程序计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。而通过异常退出,返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息。

3.5.5 附加信息

 虚拟机规范允许具体的虚拟机实现添加一些规范里没有描述的信息到栈帧之中。

 在实际的开发中,一般会把动态链接、方法返回地址与其他附加信息全部归为一类,称为栈帧信息

3.6 Java虚拟机栈的常见异常

 在Java虚拟机规范中,对这个区域规定了两种异常状况:

  • StackOverflowError异常:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出此异常。
  • OutOfMemoryError异常:如果虚拟机栈在扩容的过程中无法申请到足够的内存,将抛出此异常。

4. 本地方法栈(Native Stack)

4.1 什么是本地方法栈?

 最简单来说,Java虚拟机用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用

在这里插入图片描述

图8

4.2 什么是本地方法(Native Method)?

 一个Native Method就是一个Java调用非Java代码的接口。一个Native Method是这样一个Java方法,该方法的实现由非Java语言实现。

4.3 本地方法栈的特点?

  • 本地方法栈,也是线程私有的
  • 允许被实现成固定或者可动态拓展的内存大小。(在内存溢出方面是相同的)
     1. 如果线程请求分配的栈容量超过本地方法栈允许的最大容量,Java虚拟机将会抛出一个StackOverflowError异常。
     2. 如果本地方法栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存或者在创建新的线程时没有足够的内存去创建对应的本地方法栈,那么Java虚拟机将会抛出一个OutOfMemoryError异常。
  • 本地方法是使用C语言实现。
  • 它的具体做法是Native Method Stack中等级native方法,在Execution Engine执行时加载本地方法库。
  • 当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。它和虚拟机拥有同样的权限
  • 并不是所有的JVM都支持本地方法

5. Java堆(Heap)

5.1 Java堆存在的目的?

 堆内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存(所有的对象实例以及数组都要在堆上分配)。

注意:随着栈上分配、标量替换等优化技术逐渐成熟,所有的对象都分配在堆上也渐渐变得不是那么“绝对”了。

5.2 堆的核心概述

  • 一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。
  • Java堆区在JVM启动的时候即被创建,其空间大小也就确定了。是JVM管理的最大一块内存空间(堆内存的大小是可以调节的)。
  • 《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。
  • 所有的线程共享Java堆,在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer,TLAB)。
  • 堆,是GC(Garbage Collection,垃圾收集器)执行垃圾回收的重点区域。

 Java栈、堆和方法区之间的关系如下图:
在这里插入图片描述

图9

5.3 堆的内存结构?

 基于分代收集理论,堆空间可细分为:

 Java 7及之前堆内存逻辑上分为三部分:新生区+养老区+永久区

  • Young Generation Space 新生区 Young/New
     新生区又被划分为Eden区和Survivor区
  • Tenure Generation Space 养老区 Old/Tenure
  • Permanent Space 永久区 Perm(注意:永久代实际并不在堆中,而是在方法区中)

 Java 8及之后堆内存逻辑上分为三部分:新生区+养老区+元空间

  • Young Generation Space 新生区 Young/New
     新生区又被划分为Eden区和Survivor区
  • Tenure Generation Space 养老区 Old/Tenure
  • Meta Space 元空间 Meta(注意:元空间也不在堆中)

 堆空间在Java 7及之前的内存结构如下图:
在这里插入图片描述

图10

6. 方法区(Method Area)

6.1 方法区在哪里?

 《Java虚拟机规范》中明确说明:“尽管所有的方法区在逻辑上是属于堆的一部分,但一些简单的实现可能不会去进行垃圾收集或者进行压缩。”但对于HotSpot JVM而言,方法区还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。

 所以,方法区看作是一块独立于Java堆的内存空间

在这里插入图片描述

图11

6.2 方法区的基本理解

  • 方法区(Method Area)与Java堆一样,是各个线程共享的内存区域。
  • 方法区在JVM启动的时候被创建,并且它的实际的物理内存空间中和Java堆区一样都可以是不连续的。
  • 方法区的大小,跟堆空间一样,可以选择固定大小或者可拓展。
  • 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出的错误:Java.lang.OutOfMemoryError
  • 关闭JVM就会释放这个区域的内存。

6.3 HotSpot中方法区的演进

  • 在jdk7以前,习惯上把方法区称为永久代。jdk8开始,使用元空间取代了永久代

在这里插入图片描述

图12
  • 元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代最大的区别在于:元空间不在虚拟机设置的内存中,而是使用本地内存

6.4 方法区的内部结构

 在JDK7中方法区用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。

在这里插入图片描述

图13

7. 最后

都看到最后了,求求大家点个赞再走吧!你的支持是博主创作的最大动力。

在这里插入图片描述

  • 9
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值