JVM

JVM程序计数器&虚拟机栈&本地方法栈

JVM的学习笔记之运行时数据区的部分内容

Posted by Zhao Zihao on February 16, 2021

运行时数据区

每个线程:独立包括程序计数器、虚拟机栈、本地方法栈

线程间共享:堆、本地方法区(或称堆外内存)

程序计数器(PC寄存器)

作用:用来存储指向下一条指令的地址,也就是即将要执行的指令代码,由执行引擎读取下一条指令

特点:即没有GC(GarbageCollection),也不会OOM(OutOfMemory)

使用PC寄存器存储字节码指令地址的作用?

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

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

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

为了能够准确记录各个线程正在执行的当前字节码指令地址,最好的办法是为每一个线程都分配一个PC寄存器,这样各个线程之间便可以独立计算,从而不会出现相互干扰的情况。

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

如何保证分毫不差?

每个线程在创建后,都会产生自己的PC寄存器和栈帧,PC寄存器在各个线程之间互不影响


虚拟机栈

内存中的栈和堆

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

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

Java虚拟机栈

每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(栈中存储数据的单位),对应着一次次Java方法的调用

  • 是线程私有的
  • 生命周期和线程一致
  • 作用:主管Java程序的运行,它保存方法的局部变量(8种基本数据类型,对象的引用地址),部分结果,并参与方法的调用和返回
特点
  • 栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器
  • 每个方法的执行,伴随着入栈、出栈操作
  • 对于栈来说不存在垃圾回收问题(GC),但是存在OOM的问题
问题

1.开发中遇到的异常有哪些?

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

  • 固定大小:超过容量,抛出StackOverFlowError异常
    • 设置栈的大小:-Xss1m
  • 动态:在尝试拓展的时候无法申请到足够的内存,抛出OutOfMemoryError的异常

栈的存储单位

  • 每个线程都有自己的栈,栈中的数据都是以栈帧的格式存在
  • 在这个线程上正在执行的每个方法都各自对应一个栈帧(Stack Frame)
  • 栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息

栈的执行原理

  • 在一条活动线程中,一个时间点上,只会有一个活动的栈帧,即只有当前正在执行的方法的栈帧是有效的,这个栈帧被称为“当前栈帧”,与当前栈帧对应的方法就是“当前方法”,定义这个方法的类就是“当前类”
  • 执行引擎运行的所有字节码指令只针对当前栈帧进行操作
  • 如果该方法中调用了其他方法,对应的新的栈帧会被创建出来,放在栈的顶端,成为新的当前帧

栈帧的内部结构

五部分:局部变量表、操作数栈、动态链接、方法返回地址、一些附加信息

局部变量表
  • 最基本的存储单元是Slot(变量槽)。32位以内的类型只占用一个slot(包括returnAddress类型),64位的类型(long和double)占用两个slot
  • 定义为一个数字数组(index由0到-1),主要用于存储方法参数和定义在方法体内的局部变量。这些数据类型包括各类基本数据类型、对象引用、returnAddress类型
  • 由于局部变量表是建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题
  • 局部变量表所需的容量大小是编译期确定下来的,并保存在方法的Code属性的maximum local variables数据项中,在方法运行期间是不会改变局部变量表的大小的
  • 如果当前帧是由构造方法或者实例方法创建的,那么该对象引用this将会放在index为0的slot处,其余的参数按照参数表顺序继续排列
操作数栈

在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据。我们说Iava虚拟机的解释引擎是基于栈的执行引擎,其中的栈就是操作数栈

  • 主要用于保存计算过程中的中间结果,同时作为计算过程中变量临时的存储空间
  • 数组实现。每一个操作数栈都会有一个明确的栈深度用于存储数值,其所需的最大深度在编译期就定义好了,保存在方法的Code属性中,为max_stack的值
动态链接

指向运行时常量池的方法引用

静态链接vs动态链接

  • 静态链接(早期绑定):被调用的方法在编译期可知
  • 动态链接(晚期绑定):被调用的方法在编译期无法确定

动态类型语言:判断变量值的类型信息,变量没有类型信息,变量值才有类型信息

JS:var name = ‘hello’;

Python:info = 12.1

静态类型语言:

Java:String name = “world”;

方法返回地址

存放调用该方法的pc寄存器的值(方法A调用方法B,当方法B执行完出栈,根据引用继续执行方法A)

一些附加信息

例如,对程序调试提供支持的信息

相关面试题

  1. 举例栈溢出的情况

StackOverFlow,通过-Xss设置栈的大小:OOM

  1. 调整栈大小,能保证不出现溢出嘛?

不能,举例:无穷循环

  1. 分配的栈内存越大越好嘛?

不是,挤占其他空间

  1. 垃圾回收是否会涉及到虚拟机栈?

不会,虚拟机栈只有OOM,没有GC

本地方法(Native Method)

定义:该方法的实现由非Java语言实现,用native关键字修饰

作用:融合不同的编程语言为Java所用,它的初衷是融合C/C++程序

本地方法栈(Native Method Stack)

  • Java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用
  • 允许被实现成固定或者是可动态拓展的内存大小
  • 本地方法栈中登记本地方法,在执行引擎执行时加载本地方法库
  • 在Hotspot JVM中,直接将本地方法栈和虚拟机栈合二为一