04、JVM运行时数据区

运行时五大数据区

在这里插入图片描述

一、JVM虚拟机栈(Java Virtual Machine Stacks)

在这里插入图片描述

  • Java 虚拟机栈是线程私有的,它的生命周期与线程相同,线程启动而产生,线程结束而消亡。
  • Java 虚拟机栈是描述 Java 方法执行的内存模型,用于存储栈帧。
  • 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常。
  • 虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出 OutOfMemoryError 异常。
  • 除了 native 方法,几乎所有的 Java 方法都是通虚拟机栈来实现方法的调用和执行(需要程序计数器、堆、方法区的配合)。

1.1、栈帧

  • 每个方法执行的同时会创建一个栈帧,它是虚拟机栈的基本元素。
  • 一个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
  • 在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。执行引擎运行的所有字节码指令都只针对当前栈帧进行操作。
  • 栈帧随着方法调用而创建,随着方法结束而销毁。
  • 每一个栈帧包含的内容有局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。
1.1.1局部变量表
  • 一组变量值存储空间,用于存放方法参数和方法内定义的局部变量。
    该方法所需要分配的局部变量表的最大容量在将 Java 编译为 Class 文件时已经确定。
  • 一个局部变量表保存的是编译器可知的各种基本数据类型、对象引用和 returnAddress 类型(它指向了一条字节码指令的地址)。
  • 局部变量表的容量以变量槽为最小单位,每个变量槽可以存储32位长度的内存空间。对于64位长度的数据类型(long,double),虚拟机会以高位对齐方式为其分配两个连续的变量糙空间。
  • 局部变量表所需的内存空间在编译期间就能完成分配,在运行期间不会改变其大小。
  • 虚拟机通过索引定位的方法查找相应的局部变量
1.1.2操作数栈
  • 虚拟机栈中的一个用于计算的临时数据存储区。
  • 随着方法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/入栈操作。
1.1.3动态链接:符号引用转化为直接引用
  • 在一个class文件中,一个方法要调用其他方法,需要将这些方法的符号引用转化为其在内存地址中的直接引用,而符号引用存在于运行时常量池。
  • 每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用。
  • 这些符号引用一部分会在类加载阶段或者第一次使用时就直接转化为直接引用,这类转化称为静态解析。另一部分将在每次运行期间转化为直接引用,这类转化称为动态连接。
1.1.4返回地址
  • 一个方法开始执行后,只有两种退出方式:正常完成出口和异常完成出口
  • 正常完成出口:方法正常完成并退出,根据当前方法返回的字节码指令,这时有可能会有返回值传递给方法调用者(调用它的方法),或者无返回值。
  • 异常完成出口:方法执行过程中遇到异常,并且这个异常在方法体内部没有得到处理,导致方法退出。
  • 无论采用何种退出方式,在方法退出后,都需要返回到方法被调用的位置,方法返回时可能需要在栈帧中保存一些信息。
  • 一般来说,方法正常退出时,调用者的程序计数器的值可以作为返回地址,栈帧中会保存这个计数器值。而方法异常退出时,返回地址要通过异常处理器表来确定,栈帧中一般不保存这部分信息。

二、本地方法栈(Native Method Stacks)

本地方法栈与虚拟机栈类似,但只为 Native 方法服务。Native 方法是 Java 通过 JNI 直接调用本地 C/C++ 库,可以认为是 Native 方法相当于 C/C++ 暴露给 Java 的一个接口,Java 通过调用这个接口从而调用 C/C++ 方法。与虚拟机栈一样,本地方法栈区域也会抛出 StackOverflowError 和 OutOfMemoryError 异常。

三、程序计数器(The pc Register)

程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

3.1、程序计数器是线程私有的

JVM 的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,在同一时刻一个处理器内核只会执行一条线程,处理器切换线程时并不会记录上一个线程执行到哪个位置,所以为了线程切换后依然能恢复到原位,每条线程都需要有各自独立的程序计数器。

3.2、JVM 规范中唯一没有规定 OutOfMemoryError 情况的区域

程序计数器存储的是字节码文件的行号,而这个范围是可知晓的,在一开始分配内存时就可以分配一个绝对不会溢出的内存。

3.3、执行 Native 方法时计数器值为空

当执行 Java 方法时,程序计数器存放 Java 字节码的地址。实现上可能有两种形式,一种是相对该方法字节码开始处的偏移量,叫做 bytecode index(简称 bci)。另一种是该 Java 字节码指令在内存的地址,叫做 bytecode pointer(简称 bcp)。

Native 方法大多通过 C 实现,它的方法体不是由 Java 字节码构成,无法应用上述 Java 字节码地址的概念,也就不需要存储字节码文件的行号。

3.4、Native 方法的实际执行

Java 线程总是需要以某种形式映射到 OS 线程上,HotSpot VM 目前在大多数平台上都使用 1:1 模型(原生线程模型),也就是每个 Java 线程直接映射到一个 OS 线程上执行。此时 native 方法由原生平台直接执行。

四、堆(Heap)

堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。
在这里插入图片描述

4.1、分代概念

  • JVM 中堆空间由新生代和老年代两个区组成
  • 新生代可以划分为三个区,Eden 区,两个 Survivor 区
  • Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
  • JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。

4.2、常用参数配置

在这里插入图片描述

五、方法区(Method Area)

方法区是线程共享的,存储加载进来的每一个类的静态变量、常量和结构信息,可以看做是将类(Class)的模板信息,保存在方法区里。方法区是一种规范,不同的虚拟机的实现也不一样。从 JDK 1.8 开始,元空间(Metaspace)取代了永久代(PermGen)成为 HotSpot VM 对方法区的实现。

5.1、元空间属于本地内存

JDK8 以前,永久代是堆的一部分,和新生代、老年代的地址是连续的。JDK8 以后,元空间属于本地内存,不再属于堆的一部分,它还有一个别名叫非堆(Non-Heap),所以元空间不存在 OOM 内存溢出的情况。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值