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

文章详细分析了Java虚拟机栈的运行背景,强调栈作为程序运行时单位的角色,对比了栈与堆的区别。通过一个示例类`MyStackTest`,展示了不同方法的栈帧结构,包括无参构造器、有参构造器、静态方法和非静态方法。此外,还解释了main方法的字节码解析和局部变量表的工作方式,特别讨论了代码块中局部变量的作用域和栈空间的优化策略。
摘要由CSDN通过智能技术生成

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

关于虚拟机栈出现的背景

由于对于不同的处理器(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 类文件中在方法中可以看到有两个文件 <init> 与 main ,init 是构造器,main对应的是 main 方法

关于<init>方法

  • 无参构造方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AYtDQ0ZT-1673321219762)(关于栈的存储结构与运行原理分析.assets/image-20230110112111380.png)]

  • 有参构造方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qAywoO6K-1673321219762)(关于栈的存储结构与运行原理分析.assets/image-20230110112145675.png)]

  • 有参有返回值的静态方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5iZ5ULVV-1673321219763)(关于栈的存储结构与运行原理分析.assets/image-20230110112440601.png)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-62z0uSat-1673321219764)(关于栈的存储结构与运行原理分析.assets/image-20230110112541026.png)]

main方法解析

image-20230109211511975

对于 main 方法

名称: 地址索引为 #18 方法名 <main> 该名来自于

描述符: 地址索引为 #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 个局部变量的空间,这与本地局部变量表展现出来的结果出现了歧义。

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值