JVM之PC寄存器与栈|Java 开发实战

这是我参与更文挑战的第 2 天,活动详情查看: 更文挑战

本文正在参加「Java主题月 – Java 开发实战」,详情查看活动链接

PC寄存器

image-20210525160645682

其中堆区和元数据区或叫方法区(包括CodeCache)是线程共享的,其他区域是线程独占的。

栈主要是运行时的内存,堆是存储时的内存。

概述:PC寄存器是对物理CPU寄存器的一种抽象模拟,主要用来存储指令相关的信息,也就是下一条将要执行的指令的地址,由执行引擎读取下一条指令。不会发生GC和OOM。

作用:记录下一条要执行的字节码指令。是程序流程的指示器,异常处理、线程恢复等操作都要依赖程序计数器来完成。

使用javap -v指令查看字节码文件可以看到具体的指令地址

image-20210525231243795

优势:

多线程执行的环境下,多个线程来回切换CPU执行权,需要记录每个线程的执行行数,方便CPU切换回来时从上一次执行的位置继续执行,基于这种情况,PC寄存器也是线程私有的,防止计数器被其他线程更改。

虚拟机栈

1.概述

每个线程的创建都会有一个虚拟机栈,线程私有。栈里存储的是若干个栈帧,一个栈帧对应一个Java方法。栈的生命周期和线程一致。栈的操作只有两种,方法的执行入栈,执行结束出栈,栈的效率仅次于PC寄存器。不存在GC,存在OOM。

2.作用

负责Java程序的运行,保存方法的局部变量、部分结构,参与方法的调用和返回。

3.栈内存设置

默认情况下栈内存是动态变化的,可以使用参数-Xss设置栈的大小。

4.栈运行的原理

在一个线程中同一个时间点上,只能有一个栈帧在活动,即当前运行的方法对应的栈帧,也是栈的栈顶,如果方法A调用方法B,那么方法A对应的栈帧入栈,接着调用方法B,方法B对应的栈帧入栈,此时方法B就是当前栈帧,方法B结束后正常返回(return指令),虚拟机会丢弃当前栈帧,栈顶的栈帧变为方法A,如果方法B发生异常没有处理,那么当前栈帧会被弹出,异常会被返回给方法A,如果方法A也没有处理,那么弹出当前栈帧。**注意:**不同线程的栈帧不可以互相调用。

5.栈帧内部结构

栈帧主要有下面五个部分组成:

  • 局部变量表:

    • 是一个数字数组,主要存储方法参数、方法的局部变量和返回值。
    • 局部变了表的大小是在编译器确定的,在方法运行时不会更改。栈帧的大小主要在于局部变量表。
    • 局部变量表的基本存储单位是Slot(变量槽),32位以内的类型占用一个Slot,64位类型占用两个Slot(long和double),每一个Slot都有一个索引,指向局部变量表的值,如果一个64位的局部变量,使用前一个Slot索引就可以。如果该方法是由构造方法或实例方法(非static方法)创建的,那么index[0]保存的是this对象。Slot可以重复利用,如果一个变量超过了作用域,那么这个Slot会被下面的变量使用。
    • 局部变量表中的变量是重要的垃圾回收根节点,只要局部变量中直接或简介引用的对象都不会被回收。
  • **操作数栈:**在方法的执行过程中,根据字节码指令向操作数栈中存入(入栈)和提取(出栈)数据。主要存储计算过程中的中间结果和过程中的中间变量,还有返回值。

  • 动态链接:指向运行时常量池的方法引用,动态链接的作用就是将符号引用转换为调用方法的直接引用。

  • 方法返回地址:存储该方法的PC寄存器的值,作用是在方法退出回到该方法被调用的位置。异常返回不会返回任何返回值。

  • 附加信息(可忽略):对程序调试提高支持信息。

从代码和字节码去观察栈的内部变化

public int getSum(){
    int m = 10;
    int n = 20;
    int k = m + n;
    return k;
}
/**
* 查看字节码
*  0 bipush 10     寄存器记录指针位置 0 - 把10存入操作数栈,bipush表示以byte类型,但还是以int类存储(byte、short、char、boolean:都以int型来保存)
*  2 istore_1      寄存器记录指针位置 2 - 操作数栈出栈,存入局部变量表索引为1的位置,因为方法是非静态的,所以索引0位置存储的this,
*  3 bipush 20
*  5 istore_2
*  6 iload_1       从局部变量表取出索引为1的数据,并且存入操作数栈
*  7 iload_2       同上  此时操作数栈有两个值
*  8 iadd          出栈,iadd指令需要执行引擎翻译为机器指令,然后cpu执行操作,并且把结果存入操作数栈
*  9 istore_3      操作数栈取出栈顶元素,存入局部变量表索引为3
* 10 iload_3       从局部变量表取出索引为3的元素,存入操作数栈
* 11 ireturn       从操作数栈弹出然后返回
*/
复制代码

6.方法绑定机制

绑定就是一个字段、方法或类,在符号引用被转换为直接引用的过程,只发生一次。

早期绑定:目标方法在编译期可知,并且运行时保持不变,也就是使用静态链接的方式将符号引用转换为直接引用。

晚期绑定:被调用的方法无法在编译期确定,只能在程序运行时根据实际的类型绑定相关方法,使用的时动态链接将符号引用转换为直接引用。

7.虚方法和非虚方法

在方法编译器就确定了具体的调用版本,这个方法在调用时不可变的,那么这个方法就是非虚方法,比如:静态方法、私有方法、final修饰的方法、实例构造器、父类方法都是非虚方法,其他都是虚方法。因为方法的重写才出现的虚方法的调用。

相关指令:

  • invokestatic:调用的静态方法,非虚方法。除了final修饰的
  • invokespecial:调用init()、私有、父类方法,非虚方法。除了final修饰的。
  • invokevirtaul:调用所有的虚方法。
  • invokeinterface:调用接口方法,虚方法
  • invokedynamic:动态解析调用的方法,主要是lambad

虚方法表:

image-20210528142547549

每个方法都会存在一个虚方法表,表中存放着各个方法的实际入口。虚方法每次调用时都会向上找具体调用的方法(动态分派),为了提高性能,JVM在类的方法区建立一个虚方法表,用索引表代替查找。虚方法表在类加载的链接阶段解析的过程中创建。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享