「每日五分钟,玩转JVM」:线程独占区

255 阅读4分钟

前言

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

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

程序计数器

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

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

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

关键字:

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

虚拟机栈

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

image-20190811225812846

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

carbon

image-20190811232655530

(小声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异常

公众号