转载

憨人笔记之JVM-运行时数据区(程序计数器)

话不多说,干就完了。

憨人笔记之JVM-运行时数据区(程序计数器)

先上图,在之前的文章中,详细介绍了类加载子系统。这里先回顾一下。类加载子系统中的核心部分

  • 类加载过程(类的生命周期)包括加载、连接(验证、准备、解析)、初始化、使用、卸载这么几个过程
  • 系统类加载器,引导类加载器、拓展类加载器、应用类加载器的原理
  • 双亲委派机制的工作原理及实际意义

针对上面的提纲,回想一下是否能够记得每个类别每个小点的细节。

本章内容将着重讲述类加载子系统完成类加载之后JVM运行时数据区域。

运行时数据区

运行时数据区是虚拟机在执行Java程序的过程成中把所管理的内存划分成不同的数据区域。每个区域各司其职,有自己的生命周期,保证程序的稳定运行。有些区域随着虚拟机进程的启动而存在,而有些区域则依赖用户线程的启动和结束而建立和销毁。

如上图所示,运行时候数据区主要划分成5大块: 程序计数器(Program Counter Register)、虚拟机栈(Java Virtual Machine Stack)、本地方法栈(Native Method Stack)、方法区(Method)、堆(Heap) 。按照线程共享的角度上来看,又分为 线程共享数据区线程隔离数据区

接下来,详细说说各区域在虚拟机中承担着什么样的角色和作用。

程序计数器

程序计数器也被叫做 PC寄存器 。它是存储指令相关的现场信息,CPU只有把数据装在寄存器中,程序才能够正常执行。 程序计数器是一块很小的内存区域,同其他数据区域相比,也是运行速度最快的存储区域

它的主要作用就是 存储指向下一条指令的地址 ,也就是将要执行的指令代码,执行引擎会获取它的下一条指令。它就是当前线程所执行的字节码的行号标示。翻译成我们容易理解的话来讲,可以想象成我们在IDEA中编写代码时候的行号。而执行引擎通过这个行号,能定位到当前行的代码是什么,它将做什么事情。下面用一个伪代码来进行解释:

1	public class ProgramCounterRegisterTest {
2    		public static void main(String[] args){
3        		int i = 1;
4        		System.out.println("打印变量i的值:" + i);
5    		}
6	}
// 例如行号为3, 它将进行为变量i赋值为1的操作。这里行号3就可以看作成PC寄存器中指令的地址,int i = 1 可以看作要执行的指令
复制代码

通过javap对这个类的class字节码文件进行反编译,来看看在字节码文件中,什么表示指令地址:

憨人笔记之JVM-运行时数据区(程序计数器)

当执行引擎在执行指令的时候,就会从PC寄存器中通过指令地址"0"找到其对应的指令为iconst_1然后再进行指令的执行(后面对字节码文件会有详细的介绍)。

程序计数器作为运行时数据区的重要组成部分,具有一下特点:

  • 线程私有 ,每一个线程都会有自己独立的程序计数器,各线程之间互不干扰,生命周期伴同线程的生命周期一致
  • 如果执行的是 Java方法,会保存虚拟机正在执行的字节码文件的指令地址 ,如果执行的是 Native方法,计数器的值为空(undifined)
  • 程序的控制流指示器,分支、循环、跳转、异常处理、线程恢复等功能都依赖它来完成
  • 是整个虚拟机运行时数据区唯一不会发生OutOfMemoryError的内存区域。

程序计数器存储的字节码指令地址有何作用?为什么要记录当前线程的执行地址?

现入如今的程序都会存在多线程的情况,但是CPU一次只能执行一个线程,CPU通过时间片来控制线程的切换,时间片的时间很小,使得感觉是在多个线程在同时执行。所以,如何保证程序在多线程在线程切换的过程中,能够还原到线程上次执行的代码呢,就是通过程序计数器。

由于程序计数器是线程私有的,对于一个线程来说, 字节码解释器会通过改变程序计数器中的指令地址来明确下一条应该执行什么指令,而在多线程的情况下,在线程切换的时候,能够保证切换回来的线程能够在之前的基础上继续执行 ,从而保证程序的正常运行。以上,就是程序计数器是必不可少的也是要设计成线程私有的原因。

憨人笔记之JVM-运行时数据区(程序计数器)

程序计数器相对来说在整个运行时数据区属于比较简单的数据区域。主要就是如上图的几点内容。

不怕路歹行不怕大雨淋,心上一字敢 面对我的梦,甘愿来作憨人。 --<憨人>

原文  https://juejin.im/post/5e9ef45ef265da47c7122197
正文到此结束
Loading...