【JVM 系列】- 关于栈的存储结构与运行原理分析

63 阅读3分钟

关于栈的存储结构与运行原理分析

关于虚拟机栈出现的背景

由于对于不同的处理器(ARM/X86)内部的寄存器的设计架构是不同的,Java 语言为了实现 “Write once Run everywhere” 的口号,内部的指令都是通过栈来实现的。

栈是程序的运行时的单位,而对于堆更像是数据的存储单位。

栈:程序运行时调用的各种功能性的方法处理解决数据问题

堆:用于存放数据,比如创建的对象,静态的变量

关于程序的方法的栈帧解读

创建一个类如下代码所示:

public class MyStackTest {
    public static void main(String[] args) {
        int number = 10;
        double num = 20.0d;
        Date date = new Date();
        System.out.println("Hello World!!!");
    }
}

通过 jcalsslib 工具查看编译过的 class 文件

image-20230109211052960

对于当前的 MyStackTest.class 类文件中在方法中可以看到有两个文件 与 main ,init 是构造器,main对应的是 main 方法

关于方法

  • 无参构造方法
  • 有参构造方法
  • 有参有返回值的静态方法
  • 有参无返回值的非静态方法

main方法解析

image-20230109211511975

对于 main 方法

名称: 地址索引为 #18 方法名

该名来自于

描述符: 地址索引为 #19 形参为 java.lang.String 包路径下的 L (引用数据类型) 为 [(数组) String 返回值类型 V(void)

访问标志:[public static]

image-20230109212938163

操作数栈最大深度:2 (与数据的类型有关是固定的 32bit的类型占用一个栈单位 64bit的类型占用两个栈单位)

局部变量最大槽数: 5 (局部变量的个数,一个局部变量占用栈帧的一个槽位)

字节码长度:表示字节码所占用的行数

比如该类中的 main 占用 25 行

 0 bipush 10
 2 istore_1
 3 ldc2_w #2 <20.0>
 6 dstore_2
 7 new #4 <java/util/Date>
10 dup
11 invokespecial #5 <java/util/Date.<init> : ()V>
14 astore 4
16 getstatic #6 <java/lang/System.out : Ljava/io/PrintStream;>
19 ldc #7 <Hello World!!!>
21 invokevirtual #8 <java/io/PrintStream.println : (Ljava/lang/String;)V>
24 return

第一部分:

[0]LineNumberTable

Nr. 表示局部变量的所占槽位置

起始PC 字节码所在行号

行号 在源代码中的行号

image-20230109213025484

第二部分:

[1] LocalVariableTable

Nr. 表示局部变量的所占槽位

起始PC 字节码所在行号 该局部变量的起始作用位置

长度 25 + 0 = 25 (表示整个该方法的整个字节码的长度) 该字节码的结束作用位置

序号 字节码

名字 局部变量

起始PC + 长度 = 局部变量的作用域范围

image-20230110093715289

槽 一个局部变量占用一个槽位 局部变量表实际上在我们的内存结构上是以 数组的形式进行存储的

局部变量的重复利用

public class MyStackTest {
​
    public void test() {
        int i = 0;
        {
            int b = 0;
            b = i + 1;
        }
        int c = 0;
        c = i + 1;
    }
}

image-20230110100732307

image-20230110100753602

可以看到代码块中的变量 b 的起始PC为 4 结束长度为 4 整个作用域的大小为 8

可以看到在本地变量表中,有四个局部变量,分别是内部代码块中的 b , 非静态方法局部变量 this 以及其他两个定义变量 i 与 c ,但是 test 方法的局部变量的最大槽数却是 3 也就是说给 test 方法所在栈帧中分配了 3 个局部变量的空间,这与本地局部变量表展现出来的结果出现了歧义。

那么为什么会出现这样的问题呢?

这是因为 内部代码块中代码作用域只是作用与代码块的内部,只是在编译期间起到作用,所以为了节省栈内空间就未给栈帧中局部变量分配槽位。