【JVM】_3_运行时数据区(程序计数器、虚拟机栈、操作数栈、本地方法栈)

运行时数据区概述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yQKPPBPz-1604110818019)(https://i.loli.net/2020/09/28/86T94M71fSiX35o.png)]

image-20200928103155342

内存是非常重要的系统资源,是硬盘和CPU的中间仓库和桥梁,承载着操作系统和应用程序的实时运行。JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略,保证了JVM的高效稳定运行。

不同的JVM对于内存的划分方式和管理机制存在着部分差异。

在这里插入图片描述

Java虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。

image-20200928105730672

灰色为单线程私有的,红色为多个线程共享的,即:

  • 每个线程:独立包含程序计数器、栈、本地栈
  • 线程间共享:堆,堆外内存(永久代或元空间、代码缓存)

Every Java application has a single instance of class Runtime that allows the application to interface with the environment in which the application is running. The current runtime can be obtained from the get Runtime method.
An application cannot create its own instance of this class.

每个JVM只有一个Runtime实例。即为运行时环境。

在这里插入图片描述

线程

  • 线程是一个程序里的运行单元。JVM运行一个应用有多高线程并行的执行。

  • 在Hotspot JVM 里,每个线程都与操作系统的本地线程直接映射。

    • 当一个Java线程准备好执行以后,此时一个操作系统的本地线程也同时创建。Java线程执行终止后,本地线程也会回收。
  • 操作系统负责所有线程的安排调度到任何一个可用的CPU上。一旦本地线程初始化成功,它就会调用Java线程中的run()方法

JVM 系统线程
  • 如果使用jconsole或者任何一个调试工具,都能看到在后台有许多线程在运行。这些后台线程不包括调用public static void main(String[] args)的main线程以及所有这个main线程自己创建的线程。
    • 虚拟机线程:这种线程的操作是需要JVM达到安全点才回出现。这些操作必须在不同的线程中发生的原因是他们都需要JVM达到安全带,这样堆才不会变化。这种线程的执行类型包括stop-the-world的垃圾收集、线程栈收集、线程挂起以及偏向锁撤销。
    • 周期任务线程:这种线程是时间周期事件的体现(比如中断),他们一般用于周期性操作的调度执行。
    • GC线程:这种线程对JVM里不同种类的垃圾收集行为提供了支持。
    • 编译线程:这种线程在运行时会将字节码编译成本地代码。
    • 信号调度线程:这种线程接受信号并发送给JVM,在它内部通过调用适当的方法进行处理。

【运行时数据区】程序计数器(PC寄存器)

在这里插入图片描述

JVM中的程序计数寄存器(Program Counter Register)中,Register的命名源于CPU寄存器,寄存器存储指令相关的现场信息。CPU只有把数据装载到寄存器才能够运行。

这里并非是广义上所指的物理寄存器,或许将其翻译为PC计数器(或指令计数器)会更加贴切(也称为程序钩子),并且也不容易引起一些不必要的误会。JVM中的PC寄存器是对物理PC寄存器的一种抽象。

image-20200928164155449

作用:

PC 寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令。

PC Register介绍

  • 它是一块很小的内存空间,几乎可以忽略不记。也是运行速度最快的存储区域。

  • 在JVM规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期保持一致。

  • 在任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址;或者,如果是执行native方法,则是未指定值(undefined)

  • 它是程序控制流的指示器,分支、循环、跳转、异常处理、线程回复等基础功能都需要依赖这个计数器来完成。

  • 字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。

  • 他是唯一一个在Java虚拟机规范中没有规定任何OutOfMermoryError情况的区域


示例:

public class PCRegister {
   
    public static void main(String[] args) {
   
        int i = 10;
        int j = 20;
        int k = i + j;
    }
}

反编译,截取code部分

Code:
      stack=2, locals=4, args_size=1
         0: bipush        10
         2: istore_1
         3: bipush        20
         5: istore_2
         6: iload_1
         7: iload_2
         8: iadd
         9: istore_3
        10: return

image-20200928173209832

蓝框的数字就是指令地(或称为偏移地址),红框内为操作指令。黄框为pc寄存器保存内容,执行引擎通过读取pc寄存器内的地址获取操作指令。执行引擎操作局部变量表、操作数栈,并将操作指令(字节码指令)翻译为成机器指令,使CPU进行运算。


常见问题

  • 使用PC寄存器存储字节码指令有什么用?为什么使用pc寄存器记录当前线程的执行地址呢?

    因为CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行。

    JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令。

  • PC寄存器为什么会被设定为线程私有?

    我们都知道所谓的多线程在一个特定的时间段内只会执行其中某一个线程的方法,CPU会不停地做任务切换,这样必然会导致经常中断或恢复,如何保证分毫无差呢?为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的方法自然是为每一个线程都分配一个pc寄存器,这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况。

    由于CPU时间片轮限制,众多线程在并发执行过程中,任何一个确定的时刻,一个处理器或者多核处理器中的一个内核,只会执行每个线程中的一条指令。

    这样必然导致经常中断或恢复,如何保证分毫无差呢?每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器在各个线程之间互不影响。

补充:CPU时间片

CPU时间片即CPU分给各个程序的时间,每个线程被分配一个时间段,称作它的时间片。

宏观上:我们可以同时打开多个应用程序,每个程序并行不悖,同时运行

但在微观上:由于只有一个CPU,一次只能处理程序要求的一部分,如何处理公平,一种办法是引入时间片,每个程序轮流执行。

【运行时数据区】虚拟机栈

概述

  • 虚拟机栈出现的背景

由于跨平台性的设计,Java的指令都是根据栈来设计的。不同CPU架构不同,所以不能设计为基于寄存器的。

优点:跨平台,指令集小,编译器容易实现;

缺点:性能下降,实现同样的功能需要更多的指令。

内存中的栈与堆

栈是运行时的单位,堆是存储的单位

即:栈解决程序的运行时问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储问题,即数据怎么放、放在哪。

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

    • 是线程私有的
  • 生命周期:生命周期和线程一致。

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

  • 栈的优点

​ 栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。
在这里插入图片描述
​ JVM直接对Java栈的操作只有两个:

​ 每个方法执行,伴随着(进栈、压栈)

​ 执行结束后的出栈工作

​ 对于栈来说不存在垃圾回收问题

栈中可能出现的异常

Java 虚拟机规范允许Java栈的大小是动态的或者是固定不变的

  • 如果采用固定大小的Java虚拟机栈,那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过Java虚拟机栈运行的最大容量,Java虚拟机将会抛出一个StackOverflowError异常
  • 如果Java寻迹栈可以动态扩展,并且尝试扩展的时候无法申请到做够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个outOfMemoryError

示例:制造一个虚拟机栈上的OOM Error (StackOverflow)

// 演示栈中的异常 Exception in thread "main" java.lang.StackOverflowError
public class StackErrorTest {
   
    public static 
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值