• 2. 加载与存储指令


    加载与存储指令

    作用

    加载和存储指令用于将数据从栈帧的局部变量表和操作数栈之间来回传递。

    常用指令

    1. 【局部变量压栈指令】将一个局部变量加载到操作数栈: xload、 xload_ (其中x为i、l、f、d、a,n为0到3)
    2. 【常量入栈指令】 将一个常量加载到操作数栈: bipush、 sipush、 ldc、ldc_w、ldc2_w、
      aconst_null、 iconst_m1、iconst_、lconst_、fconst_、 dconst_
    3. 【出栈装入局部变量表指令】将一个数值从操作数栈存储到局部变量表: xstore、xstore_ (其中x为i、1、f、d、a,n为0到3);xastore(其中x为i、l 、f、d、a、b、c、s)
    4. 扩充局部变量表的访问索引的指令: wide。

    上面所列举的指令助记符中,有一部分是以尖括号结尾的(例如iload_)。这些指令助记符实际上代表了一组指令(例如iload_代表 了iload_0、iload_1、iload_2和iload_3这几个指令)。这几组指令都是某个带有一个操作数的通用指令(例如iload) 的特殊形式,对于这若干组特殊指令来说,它们表面上没有操作数,不需要进行取操作数的动作,但操作数都隐含在指令中

    比如:
    iload_0:将 局部变量表中索引为0位置上的数据压入操作数栈中。
    iload 0:将局部变量表中索引为0位置上的数据压入操作数栈中。
    iload 4:将局部变量表中索引为4位置上的数据压入操作数栈中。

    除此之外,它们的语义与原生的通用指令完全一致(例如iload_0的语义与操作数为0时的iload 指令语义完全一致)。在尖括号之间的字母指定了指令隐含操作数的数据类型,代表非负的整数, 代表是int类型数据,代表long类型,代表float类型, 代表double类型。

    操作byte、char、 short和boolean类型数据时,经常用int类型的指令来表示。

    再谈操作数栈与局部变量表

    操作数栈(Operand Stacks)

    我们知道,Java字节码是Java虚拟机所使用的指令集。因此,它与Java虚拟机基于栈的计算模型是密不可分的。
    在解释执行过程中,每当为Java方法分配栈桢时,Java虚拟机往往需要开辟一块 额外的空间作为操作数栈,来存放计算的操作数以及返回结果
    具体来说便是:执行每一条指令之前,Java虚拟机要求该指令的操作数已被压入操作数栈中。在执行指令时,Java虚拟机会将该指令所需的操作数弹出,并且将指令的结果重新压入栈中。

    在这里插入图片描述

    以加法指令iadd 为例。假设在执行该指令前,栈顶的两个元素分别为int值1和int值2,那么iadd指令将弹出这两个int, 并将求得的和int值3压入栈中。

    在这里插入图片描述

    由于iadd指令只消耗栈顶的两个元素,因此,对于离栈顶距离为2的元素,即图中的问号,iadd指令并不关心它是否存在,更加不会对其进行修改。

    局部变量表(Local Variables)

    Java方法栈桢的另外一个重要组成部分则是局部变量区,字节码程序可以将计算的结果缓存在局部变量区之中
    实际上,Java虚拟机将局部变量区当成一个数组,依次存放this指针(仅非静态方法),所传入的参数,以及字节码中的局部变量。
    和操作数栈一样,long类型以及double 类型的值将占据两个单元,其余类型仅占据一个单元。

    在这里插入图片描述

    举例:

    public void foo(long 1, float f) {
    	{
    		inti=0:
    	}
    	{
    	String S = "Hello, World":
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    对应的图示:

    在这里插入图片描述

    在栈帧中,与性能调优关系最为密切的部分就是局部变量表。局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收。

    局部变量压栈指令

    局部变量压栈指令将给定的局部变量表中的数据压入操作数栈。
    这类指令大体可以分为:

    > xload_(x为i、1、f、d、a,n为0到3)
    > xload (x为i、1、f、d、a)
    说明:在这里,x的取值表示数据类型。

    指令xload_n表示将第n个局部变量压入操作数栈,比如iload_1、fload_0、aload_0等指令。其中aload_ n表示将一个对象引用压栈。
    指令xload通过指定参数的形式,把局部变量压入操作数栈,当使用这个命令时,表示局部变量的数量可能超过了4个,比如指令iload、fload等。

    //1. 局部变量压栈指令
    public void load(int num, Object obj , long count , boolean flag, short[] arr) {
        System.out.println(num) :
        System.out.println(obj):
        System.out.println(count):
        System.out.println(flag):
        System.out.println(arr):
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    常量入栈指令

    常量入栈指令的功能是将常数压入操作数栈,根据数据类型和入栈内容的不同,又可以分为const系列、push系列和ldc指令。

    指令const系列:用于对特定的常量入栈,入栈的常量隐含在指令本身里。指令有: iconst_ (i从-1到5)、lconst_ (1从0到1)、 fconst_ (f从0到2)、 dconst_ (d从0到1)、aconst_null。
    比如:

    • iconst_m1将-1压入操作数栈:
    • iconst_x (x为0到5) 将x压入栈;
    • lconst_0、lconst_1分别将长整数0和1压入栈:
    • fconst_0、fconst_1、fconst_2分别将浮点数0、1、2压入栈:
    • dconst_0和dconst_ 1分 别将double型0和1压入栈:
    • aconst_null将null压 入操作数栈:

    从指令的命名上不难找出规律,指令助记符的第一个字符总是喜欢表示数据类型,i表示整数,l表示长整数,f表示浮点数,d表示双精度浮点,习惯上用a表示对象引用。如果指令隐含操作的参数,会以下划线形式给出。

    int i = 3 ; iconst_ 3
    int i = 6 ; bipush 6
    int k = 32768 ldc …

    指令push系列:主要包括bipush和sipush。它们的区别在于接收数据类型的不同,bipush接收8位 整数作为参数,sipush接收16位整数,它们都将参数压入栈。

    指令ldc系列:如果以上指令都不能满足需求,那么可以使用万能的ldc指令,它可以接收一个8位的参数,该参数指向常量池中的int、float或者String的索引,将指定的内容压入堆栈。
    类似的还有ldc_w,它接收两个8位参数,能支持的索引范围大于ldc。
    如果要压入的元素是long或者double类型的,则使用ldc2_w指令,使用方式都是类似的。

    在这里插入图片描述

    在这里插入图片描述

    总结如下

    在这里插入图片描述

    注意:常量入栈指令中的n和局部变量压栈指令中的n不一样,本次的n代表数值或者对象,而不是局部变量表中的下标

    出栈入局部变量表指令

    出栈装入局部变量表指令用于将操作数栈中栈顶元素弹出后,装入局部变量表的指定位置,用于给局部变量赋值。

    这类指令主要以store的形式存在,比如xstore (x为i、 l、f、d、a)、xstore_n (x为i、l、f、d、a,n为0至3)。

    • 其中,指令istore_n将从操作数栈中弹出一个整数,并把它赋值给局部变量索引n位置。
    • 指令xstore由于没有隐含参数信息,故需要提供一个byte类型的参数类指定目标局部变量表的位置。

    说明:
    一般说来,类似像store这样的命 令需要带一个参数,用来指明将弹出的元素放在局部变量表的第几个位置。但是,为了尽可能压缩指令大小,使用专门的istore_1指令表示将弹出的元素放置在局部变量表第1个位置。类似的还有istore_0、istore_2、istore_3,它们分别表示从操作数栈顶弹出一个元素,存放在局部变量表第0、2、3个位置。
    由于局部变量表前几个位置总是非常常用,因此
    这种做法虽然增加了指令数量,但是可以大大压缩生成的字节码的体积
    , 如果局部变量表很大,需要存储的槽位大于3,那么可以使用istore指令, 外加一个参数,用来表示需要存放的槽位位置。

    在这里插入图片描述

    ​ 该方法被调用的时候,形式参数k和d都是有确定的值,由于该方法不是静态方法,所以局部变量表中的第一个位置(槽位)存储this;第二个位置存储k具体的值;第三个和第四个位置储存d具体的值,由于d是double类型,所以需要占据两个槽位。
    iload_1是将局部变量表中下标为1的k值取出来压入操作数栈中,然后iconst_2是将常量池中的整型值2压入操作数栈,iadd让操作数栈弹出的k值和整型值2执行相加操作,之后将相加的结果值m压入操作数栈中,在执行弹栈和压栈操作之后,操作数栈中就没有k值和2了,只有m值了,然后istore_4是将操作数栈中的m值弹出栈,然后放在局部变量表中下标为4的位置,idc2_w #13代表将long型值12压入操作数栈,istore5是将值12弹栈之后放入局部变量表中下标为5的位置,由于12是long型,所以占据两个位置(槽位),ldc #15代表将字符串aaaaaa压入操作数栈,astore 7代表将字符串aaaaaa弹栈之后放入局部变量表中下标为7的位置,idc #16代表将float类型数据10.0压入操作数栈,fstore 8代表将10.0弹出栈,然后放入局部变量表中下标为8的位置,idc2_w #17代表将10.0压入操作数栈,dstore2代表将10.0弹出栈,之后将10.0放入下标为2和3的操作,毕竟这是double类型数据

    在这里插入图片描述

    **注意:**在方法没有运行的时候,根据字节码文件就可以计算出需要几个槽位

  • 相关阅读:
    力扣-1984. 学生分数的最小差值
    硅谷甄选运营平台-笔记
    【洛谷 P1105】平台 题解(结构体+暴力枚举)
    Spring Boot中的依赖注入和自动注入
    Java设计模式面试题及答案(持续更新。。。)
    【算法】二分查找-20231121
    Linux系统----伪设备
    jQuery 入门-----第三节:jQuery事件
    基于STM32 CubeMX利用RTC实现带温湿度模块的万年历
    Java实现操作阿里云OSS云存储详解,含配置和完整代码
  • 原文地址:https://blog.csdn.net/u013559614/article/details/126079219