目录

一、概述

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


一、概述

(一)、作用

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

(二)、常用指令

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

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

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

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

(一)、操作数栈

字节码在解释执行过程中,每当为Java方法分配栈桢时,Java虚拟机往往需要开辟一块额外的空间作为操作数栈,来存放计算的操作数以及返回结果。

具体来说便是:执行每一条指令之前,Java虚拟机要求该指令的操作数已被压入操作数栈中。在执行指令时,Java虚拟机会将该指令所需的操作数弹出,并且将指令的结果重新压入栈中。

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

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

(二)、局部变量表

【a】概述

Java方法栈桢的另外一个重要组成部分则是局部变量区,字节码程序可以将计算的结果缓存在局部变量区之中。Java虚拟机将局部变量区当成一个数组,依次存放this指针(仅非静态方法),所传入的参数,以及字节码中的局部变量。

和操作数桟一样,long类型以及double类型的值将占据两个单元,其余类型仅占据一个单元。

如下图:

public void foo(long l, float f){{int i = 0;}{String s = "Hello,World";}
}

上述程序对应的局部变量表如下图所示:

  • 第一个是this,只会在非静态方法中才有;
  • 第二个l是long型,占据两个槽位;
  • 第三个 f 是float型,占据一个槽位;
  • 由于静态代码块的原因,i和s共享槽位,因此它两共用一个槽位;

在栈帧中,与性能调优关系最为密切的部分就是局部变量表。局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收。在方法执行时,虚拟机使用局部变量表完成方法的传递。

【b】局部变量压栈指令

局部变量压栈指令将给定的局部变量表中的数据压入操作数栈。

这类指令大体可以分为:

    1. xload_(x为i、l、f、d、a,n为0到3),x的取值表示数据类型;
    2. xload (x为i、l、f、d、a),x的取值表示数据类型;
  • 指令xload_n表示将第n个局部变量压入操作数栈,比如iload_1、fload_0、aload_0等指令。其中aload_n表示将一个对象引用压栈。
  • 指令xload通过指定参数的形式,把局部变量压入操作数栈,当使用这个命令时,表示局部变量的数量可能超过了4个,比如指令iload、fload等。
public void load(int num, Object object, long count, boolean flag, short[] arr) {System.out.println(num);System.out.println(object);System.out.println(count);System.out.println(flag);System.out.println(arr);
}

查看其字节码分析如下:

0 getstatic #2 <java/lang/System.out>3 iload_1   //将局部变量表中的索引为1的整形变量压入操作数栈中4 invokevirtual #3 <java/io/PrintStream.println>7 getstatic #2 <java/lang/System.out>
10 aload_2   //将局部变量表中的索引为2的对象类型变量压入操作数栈中
11 invokevirtual #4 <java/io/PrintStream.println>
14 getstatic #2 <java/lang/System.out>
17 lload_3   //将局部变量表中的索引为3的long类型变量压入操作数栈中
18 invokevirtual #5 <java/io/PrintStream.println>
21 getstatic #2 <java/lang/System.out>
24 iload 5   //将局部变量表中的索引为5的int类型变量压入操作数栈中
26 invokevirtual #6 <java/io/PrintStream.println>
29 getstatic #2 <java/lang/System.out>
32 aload 6   //将局部变量表中的索引为6的对象类型变量压入操作数栈中
34 invokevirtual #4 <java/io/PrintStream.println>
37 return

并且我们注意到,long类型的count是占用两个槽位的,所以flag的索引从5开始的:

【c】常量入栈指令

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

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

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

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

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

c、指令ldc系列:如果以上指令都不能满足需求,那么可以使用万能的ldc指令,它可以接收一个8位的参数,该参数指向常量池中的int、float或者String的索引,将指定的内容压入堆栈。

类似的还有ldc_w,它接收两个8位参数,能支持的索引范围大于ldc。如果要压入的元素是long或者double类型的,则使用ldc2_w指令,使用方式都是类似的。总结如下:

类型

常数指令

范围

int(boolean,byte,char,short)

iconst

[-1,5]

bipush

[-128,127]

sipush

[-32768,32767]

ldc

any int value

long

lconst

0,1

ldc

any long value

float

fconst

0,1,2

ldc

any float value

double

dconst

0,1

ldc

any double value

reference

aconst

null

ldc

String literal,Class lteral

int类型举例:

public void pushConst() {int a = -1;int b = 5;int c = 6;int d = 127;int e = 128;int f = 32767;int g = 32768;
}

查看其字节码分析如下:

0 iconst_m1   //将-1压入操作数栈中1 istore_1   //将a的值,即-1存入局部变量表索引为1的位置2 iconst_5   //将5压入操作数栈中3 istore_2   //将b的值,即5存入局部变量表索引为2的位置4 bipush 6   //将6压入操作数栈中6 istore_3   //将c的值,即6存入局部变量表索引为3的位置7 bipush 127   //将127压入操作数栈中9 istore 4   //将d的值,即127存入局部变量表索引为4的位置
11 sipush 128   //将128压入操作数栈中,超出bipush的范围,使用sipush
14 istore 5   //将e的值,即128存入局部变量表索引为5的位置
16 sipush 32767   //将32767压入操作数栈中
19 istore 6   //将f的值,即32767存入局部变量表索引为6的位置
21 ldc #7 <32768>   //将32768压入操作数栈中
23 istore 7   //将g的值,即32768存入局部变量表索引为7的位置
25 return   //方法返回

如下是jclasslib查看到的局部变量表信息,对比上述字节码分析:

其他类型举例:

public void constLdc() {long a1 = 1;long a2 = 2;float b1 = 2;float b2 = 3;double c1 = 1;double c2 = 2;Date d = null;
}

查看其字节码分析如下:

0 lconst_1   //将long类型的常量1压入操作数栈中,即a1的值1 lstore_1   //将a1的值1存入局部变量表中索引为1的位置2 ldc2_w #8 <2>   //因为是long类型的数据,所以使用ldc2_w,将2压入操作数栈中,即a2的值5 lstore_3   //将a2的值2存入局部变量表中索引为3的位置[long占用两个槽位]6 fconst_2   //将float类型的常量2压入操作数栈中,即b1的值7 fstore 5   //将b1的值2存入局部变量表中索引为5的位置[long占用两个槽位]9 ldc #10 <3.0>   //float类型,使用ldc指令,将3.0压入操作数栈中
11 fstore 6   //将b2的值3.0存入局部变量表中索引为6的位置
13 dconst_1   //将double类型的常量1压入操作数栈中,即c1的值
14 dstore 7   //将c1的值1存入局部变量表中索引为7的位置
16 ldc2_w #11 <2.0>   //因为是double类型的数据,所以使用ldc2_w,将2.0压入操作数栈中,即c2的值
19 dstore 9   //将c2的值1存入局部变量表中索引为9的位置[double占用两个槽位]
21 aconst_null   //将null压入操作数栈中,即d的值
22 astore 11   //将d的值null存入局部变量表中索引为11的位置
24 return   //方法返回

对比jclasslib生成的局部变量表:

【d】出栈装入局部变量表指令

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

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

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

说明:

一般说来,类似像store这样的命令需要带一个参数,用来指明将弹出的元素放在局部变量表的第几个位置。但是,为了尽可能压缩指令大小,使用专门的istore_1指令表示将弹出的元素放置在局部变量表第1个位置。类似的还有 istore_0、istore_2、istore_3,它们分别表示从操作数栈顶弹出一个元素,存放在局部变量表第0、2、3个位置。

由于局部变量表前几个位置总是非常常用,因此这种做法虽然增加了指令数量,但是可以大大压缩生成的字节码的体积。如果局部变量表很大,需要存储的槽位大于3,那么可以使用istore指令,外加一个参数,用来表示需要存放的槽位位置。

看一个示例:

public void store(int k, double d) {int m = k + 2;long l = 12;String str = "hello world";float f = 10.0F;d = 10;
}

查看其字节码分析如下:

0 iload_11 iconst_22 iadd3 istore 45 ldc2_w #13 <12>8 lstore 5
10 ldc #15 <hello world>
12 astore 7
14 ldc #16 <10.0>
16 fstore 8
18 ldc2_w #17 <10.0>
21 dstore_2
22 return

这里我们使用图解的方式来分析一下上述字节码指令:

对比jclasslib生成的局部变量表:

再来看一个槽共用的示例:

public void foo(long l, float f) {{int i = 0;}{String str = "hello world";}
}

查看其字节码分析如下:

0 iconst_0
1 istore 4
3 ldc #15 <hello world>
5 astore 4
7 return

还是通过图解的方式来分析一下:

由上图我们可以看到:

  • 操作数栈最大深度为5;
  • 两个代码块共用了一个槽位,在第一个代码块执行完毕之后第二个代码块会复用第一个代码块的槽位;

字节码指令之加载与存储指令相关推荐

  1. 深入理解Java虚拟机——加载和存储指令

    目录 一.加载和存储指令的概述 二.加载和存储指令的内容 三.加载和存储指令的示例 一.加载和存储指令的概述 加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输. 二.加载和存储指令的 ...

  2. 单寄存器加载与存储指令

    这种指令用于把单一的数  传入或者传出一个寄存器.支持的数据类型有字(32 位 ) .半字(16 位)  和字节.常用的单寄存器加载与存储指令包括: LDR/STR            字数据加载/ ...

  3. ARMv8体系结构基础03:加载和存储指令

    目录 1 A64指令集概述 1.1 A64指令集特征 1.1.1 指令定长 1.1.2 可使用64位指针 1.1.3 使用一致的编码结构(consistent encoding scheme) 1.1 ...

  4. 【ARMv8 编程】A64 内存访问指令——内存加载指令

    与所有先前的 ARM 处理器一样,ARMv8 架构是一种加载/存储架构.这意味着没有数据处理指令直接对内存中的数据进行操作.数据必须首先被加载到寄存器中,修改,然后存储到内存中.该程序必须指定地址.要 ...

  5. ARM汇编:加载和存储指令集(六大类)---LDR(ADR)、LDRB、LDRH、STR、STRB、STRH

    ARM的六大类指令集---LDR.LDRB.LDRH.STR.STRB.STRH ARM微处理器支持加载/存储指令用于在寄存器和存储器之间传送数据,加载指令用于将存储器中的数据传送到寄存器,存储指令则 ...

  6. 借由ARM CORTEX-M芯片分析C程序加载和存储模型

    https://zhuanlan.zhihu.com/p/22048373 写文章 借由ARM CORTEX-M芯片分析C程序加载和存储模型 王小军 1 年前 阿军最近在忙着血氧手环嵌入式系统的技术预 ...

  7. 【Android 插件化】Hook 插件化框架 ( 从源码角度分析加载资源流程 | Hook 点选择 | 资源冲突解决方案 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  8. Python之pandas数据加载、存储

    Python之pandas数据加载.存储 0. 输入与输出大致可分为三类: 0.1 读取文本文件和其他更好效的磁盘存储格式 2.2 使用数据库中的数据 0.3 利用Web API操作网络资源 1. 读 ...

  9. Mybatis3源码分析(05)-加载Configuration-加载MappedStatement

    2019独角兽企业重金招聘Python工程师标准>>> Mybatis3源码分析(05)-加载Configuration-加载MappedStatement 博客分类: java m ...

  10. springboot集成mybatis源码分析-启动加载mybatis过程(二)

    springboot集成mybatis源码分析-启动加载mybatis过程(二) 1.springboot项目最核心的就是自动加载配置,该功能则依赖的是一个注解@SpringBootApplicati ...

最新文章

  1. python3 pycharm 远程调试 启动报错 ValueError: source code string cannot contain null bytes
  2. leetcode算法题--二叉搜索树的后序遍历序列
  3. python pos函数_使用python+sklearn实现特征提取
  4. wxpython使用matplot_测试怎么将MatPlotLib嵌入到wxPython中
  5. 针对Selenium环境搭建的一些小解说
  6. 拉格朗日插值法(Lagrange)
  7. 远控免杀专题(29)-C#加载shellcode免杀-5种方式(VT免杀率8-70)
  8. matlab频率阻抗,有分析阻抗的matlab脚本吗?
  9. 可怕!公司部署了一个东西,悄悄盯着你……
  10. BottomupSort算法 c++代码实现
  11. 如何优雅地关闭Kubernetes集群中的Pod
  12. 动态截屏软件jpg格式
  13. 哇哦,教你如何使用jumpserver堡垒机添加windows资产
  14. [转载]样式表编写效率手册 [ 日期:2004-10-15 ] [ 来自:Plod ]
  15. linux应用/软件设置为系统服务
  16. Mac系统好用快捷键简记
  17. linux ida和idr分配机制
  18. H3C设备运行状态查询常用命令(建议收藏)
  19. libxml主要函数说明
  20. 一个成功人士的创业心得

热门文章

  1. Docker MySQL 8 慢查询日志监控详解
  2. java复杂的代码做程序_摆脱复杂烧脑的程序代码,利用快速开发平台轻轻松松做软件...
  3. 两数相除 leetcode
  4. 154.寻找旋转排序数组中的最小值II
  5. HttpServlet介绍
  6. 【GNN框架系列】DGL第一讲:使用Deep Graph Library实现GNN进行节点分类
  7. Deep Learning-Deep feedforward network
  8. 想去机器学习初创公司做数据科学家?这些问题值得你三思!
  9. Linux下获取时间差(毫秒级)
  10. java连接sftp的几种方式_Java使用SFTP和FTP两种连接服务器的方式实现对文件的上传下载...