每日五分钟玩转JVM线程独占区

6次阅读

共计 1502 个字符,预计需要花费 4 分钟才能阅读完成。

前言

如果我们对计算机组成有所了解,那么我们一定会知道在计算机中有一块儿特殊的区域,称之为寄存器,寄存器包括了指令寄存器和程序计数器,这两样位于 CPU 中,作为程序运行的 大脑 来控制程序的运行和流转。

而在 JVM 中,作为一种虚拟机,JVM 没有指令寄存器,它是基于 栈 + 程序计数器 的体系结构来完成方法的执行,之所以这么去设计一方面是为了指令集的紧凑,一方面是有些平台上的寄存器很少或者根本没有,而且以处理器架构的角度来说,设计一套通用的寄存器指令是很困难的,而且还有一方面的考量就是有助于运行时某些虚拟机实现的动态编译器和即时编译器的代码优化。

程序计数器

我们在使用 IDE 写代码的时候,旁边经常会有行数,方便我们去阅读我们自己的代码,去定位我们代码的位置,而程序计数器是给 JVM 执行字节码过程中看的 行号指示器 ,只不过他其中存的并非是行号,而是 执行中 虚拟机 字节码指令 的地址,也正是因为计数器中存储的仅仅是一条执行中的指令地址,使用的内存是及其有限的,所以这个区域是唯一一个 没有 OOM的区域。字节码解释器通过改变程序计数器中的地址来寻找对应的指令来完成对程序的控制(这里具体我们会在分析指令集时去详细的深入了解,这里仅仅点到为止)

这里需要注意一点,如果是本地 Native 方法,该计数器的值是 Undefined,其实也很好理解,人家压根不属于 JVM 去管理,你凭什么去记录。。这里我们使用的可以说是Native 方法提供出的一个接口,具体的实现是通过 C 来完成的。

上节课我们说到多线程的实现是基于 时分复用 来实现的,为了每个线程的运行的互不干扰和有序性,程序计数器必须保证在切换时能够回到正确的位置,所以它 必须 必然 是线程独占区的一份子。

关键字:

  • 行号指示器
  • 执行中字节码指令地址
  • 没有 OOM
  • Native 方法不归他管
  • 线程独享

虚拟机栈

虚拟机栈是 Java方法 运行时的内存模型,包括了局部变量表,操作数栈,动态连接,方法返回地址以及一些附加信息,每个方法在被执行的时候会在虚拟机栈中创造一个 栈帧

栈帧在执行的时候创建压入栈,在完全执行完成后就会进行弹栈,下面是一个小???? 去直观的看一下过程~

(小声 BB 一句,这里不会制作 GIF 图,凑合着看吧。。)

下面简单介绍一下栈帧中的内容,这一部分我们会在了解字节码执行引擎的时候会进行更为详细的讲解。

  • 局部变量表

    局部变量表(Local Variable Table)是一组 变量值存储空间 ,用于 方法参数 和方法内部定义的 局部变量,包含了编译器可知的基本数据类型(byte,short,int,long,double,float,char,boolean),对象引用(reference)以及 returnAddress 类型。

  • 操作数栈

    同样是一个 LIFO(Last In First Out,后入先出)的栈结构,用于计算的临时存储区,前面提过 JVM 是没有寄存器的,而操作数栈的作用就是让 JVM 的指令是从中获取操作数。

  • 动态连接

    每个栈帧都包含一个运行时常量池中该栈帧 所属方法的引用,持有这个引用就是为了支持方法在调用过程中的动态连接

本地方法栈

和虚拟机栈相似,本地方法栈面向的对象不是 JVM 字节码,而是本地的 Native 方法,比如 String 类中的 intern 方法,Native 方法更像是本地方法的提供的一个接口,而本地方法栈正是管理这些接口的一个区域。

public native String intern();

异常

本地方法栈和虚拟机栈同为栈结构之中,他们会面对两种异常,当线程请求的栈深度大于虚拟机所允许的深度时,将抛出 StackOverflowError,如果栈可以动态扩展,如果扩展时无法申请到足够的内存,会抛出 OOM 异常

公众号

正文完
 0