Java教程

JVM上篇:运行时数据区及程序计数器

本文主要是介绍JVM上篇:运行时数据区及程序计数器,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

运行时数据区及程序计数器

1.概述

本文主要讲的是运行时数据区,它是在类加载完成后的阶段,也就是下图蓝色框框圈住的那部分。

image-20220222232621390

当一个class文件,经过load,link,initialze被类加载器子系统加载进方法区后,就会用到执行引擎对我们的类进行使用(解释执行),同时执行引擎也会用到运行时数据区

image-20220222233604419

举个厨师做饭的例子,来描述运行时数据区和执行引擎的关系

我们把大厨后面的东西(切好的菜,刀,调料),比作是运行时数据区。而厨师可以类比于执行引擎,将通过准备的东西进行制作成精美的菜品

image-20220222234042559

内存是非常重要的系统资源,是CPU和硬盘交流的中间仓库以及桥梁,承载着操作系统和应用程序的实时运行,JVM内存布局规定了Java在运行过程中内存的申请,分配,管理的策略,保证了Jvm高效,稳定的运行,不同的Jvm对内存的划分方式和管理机制也存在着部分差异。

来自阿里的一张细节的运行时数据区的结构图

image-20220222235156235

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

经典的JVM内存布局

image-20220222234746162

上图灰色区域为单独线程私有的,红色区域为多个线程共享的,即

  • 每个线程:独立拥有程序计数器,虚拟机栈,本地方法栈
  • 多线程间共享:堆,对外内存(方法区、元空间都算)

image-20220223001136141

每个JVM只有一个Runtime实例。即为运行时环境,相当于内存结构的中间的那个框框:运行时环境。

img

2.线程

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

在HotSpot VM 里,每个线程都与操作系统的本地线程直接映射

  • 当一个Java线程准备好执行以后,此时一个操作系统的本地线程也同时创建,Java线程终止执行以后,本地线程也会回收。

操作系统负责所有线程的安排调度到任何一个可用的CPU上,一旦本地线程初始化成功,它就会调用Java线程中的run()方法。

注:当JVM中最后一个非守护线程正常或异常退出后,jvm才会结束生命周期。

3.JVM系统线程

如果你使用console或者是任何一个调试工具,都能看到在后台有许多线程在运行。这些后台线程不包括调用public static void main(String[] args)的main线程以及所有这个main线程自己创建的线程。

这些主要的后台系统线程在Hotspot JVM里主要是以下几个:

  • 虚拟机线程:这种线程的操作是需要JVM达到安全点才会出现。这些操作必须在不同的线程中发生的原因是他们都需要JVM达到安全点,这样堆才不会变化。这种线程的执行类型包括"stop-the-world"的垃圾收集,线程栈收集,线程挂起以及偏向锁撤销。

  • 周期任务线程:这种线程是时间周期事件的体现(比如中断),他们一般用于周期性操作的调度执行。

  • GC线程:这种线程对在JVM里不同种类的垃圾收集行为提供了支持。

  • 编译线程:这种线程在运行时会将字节码编译成到本地代码。

  • 信号调度线程:这种线程接收信号并发送给JVM,在它内部通过调用适当的方法进行处理。

4.程序计数器(PC寄存器)

概述

JVM中的程序计数寄存器(Program Counter Register)中,Register的命名源于CPU的寄存器,寄存器存储指令相关的现场信息。CPU只有把数据装载到寄存器才能够运行。这里,并非是广义上所指的物理寄存器,或许将其翻译为PC计数器(或指令计数器)会更加贴切(也称为程序钩子),并且也不容易引起一些不必要的误会。JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟。

0b5b92d3-46c2-4e75-984c-f8b375353265

PC寄存器是每个线程一份的,PC寄存器是用来存储指向下一条指令的地址,也就是即将要执行的指令代码,由执行引擎读取下一条指令

102a4023-a751-4755-bf02-6d8b60812c78

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

在java虚拟机中每个线程都有自己的程序计数器,来计数自己执行到哪里了,为线程私有,它的生命与线程的生命周期保持一致。

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

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

它是唯一一个在Java虚拟机规范中没有规定任何OOM情况的区域,也不存在GC

GC可能发生在 方法区和堆区

OOM可能发生在 方法区 堆区 虚拟机栈

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

5.PC寄存器使用举例说明

java代码

public int minus(){
    intc = 3;
    intd = 4; 
    return c - d;
}

main方法对应字节码指令:

0: iconst_3
1: istore_1
2: iconst_4
3: istore_2
4: iload_1
5: iload_2
6: isub
7: ireturn

PC寄存器存储字节码指令地址有什么用呢?

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

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

pc寄存器为社么设置为线程私有的?

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

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

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

CPU时间片

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

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

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

image-20220223230501449

这篇关于JVM上篇:运行时数据区及程序计数器的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!