Java虚拟机栈之操作数栈

本文深入解析Java虚拟机(JVM)的操作数栈,探讨其在方法调用和执行中的作用,以及不同数据类型如int、long、float等在操作数栈中的处理方式。通过具体代码示例,分析了各种数据类型在不同范围内的编译指令,如iconst、bipush、sipush和ldc等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JAVAP指令集

环境:jdk1.8

 

首先来一张图看看jvm的运行时数据区如下(有个印象即可):

什么是操作数栈?

- 与局部变量表一样,均以字长为单位的数组。不过局部变量表用的是索引,操作数栈是弹栈/压栈来访问。操作数栈可理解为java虚拟机栈中的一个用于计算的临时数据存储区。
- 存储的数据与局部变量表一致含int、long、float、double、reference、returnType,操作数栈中byte、short、char压栈前(bipush)会被转为int。
- 数据运算的地方,大多数指令都在操作数栈弹栈运算,然后结果压栈。
- java虚拟机栈是方法调用和执行的空间,每个方法会封装成一个栈帧压入占中。其中里面的操作数栈用于进行运算,当前线程只有当前执行的方法才会在操作数栈中调用指令(可见java虚拟机栈的指令主要取于操作数栈)。
- int类型在-1~5、-128~127、-32768~32767、-2147483648~2147483647范围分别对应的指令是iconst、bipush、sipush、ldc(这个就直接存在常量池了)

举例一个加法过程:

package lime.jvm._002._004._002;

/**
 * @Author : Liangmy
 * @Description :
 * @Date : Created in 2019/12/28 上午12:04
 * @Modified By :
 */
public class Hello {
    public static void main(String[] args) {
        Hello hello = new Hello();
        int testVal = hello.test();
    }


    public int test() {
        //int类型
        int a = 5 + 10;//验证直接相加在编译阶段已合并完结果
        int b = a + 3;//探究变量与常量的相加过程
        b = b + 6;//验证int不在-1~5,在-128~127范围的指令是bipush
        b = b + 128; //验证int不在-128~127,在-32768~32767范围的指令是sipush
        b = b + 32768;//验证int不在-32768~32767,在-2147483648~2147483647范围的指令是ldc(ldc:从常量池取并压栈,所以这个范围的int是存在常量池)
        // short     //验证byte、short、char在操作数栈压栈前均会转为int
        short a_s = 5 + 10;
        short b_s = (short) (a_s + 3);
        //float类型      //以下验证float、double、String均是从常量池中取出(均使用了ldc、ldc_w、ldc2_w其中一个)
        float a_f = 5.00F + 10.00F;
        float b_f = a_f + 3.00F;
        //double类型
        double a_d = 5.00D + 10.00D;
        double b_d = a_d + 3.00D;
        //String类型
        String a_str = "a" + "b";
        String b_str = a_str + "c";
        return b;
    }
}

首先这个会保存在方法的Code 属性中,javac Hello.java编译,然后javap -verbose Hello.class反编译分析test()方法如下:

localhost:_002 liangmy$ javap -verbose Hello
警告: 二进制文件Hello包含lime.jvm._002._004._002.Hello
Classfile /Users/liangmy/ideaProjects/lime/src/main/java/lime/jvm/_002/_004/_002/Hello.class
  Last modified 2019-12-28; size 695 bytes
  MD5 checksum cef851e19232218b32b9065873aefe6a
  Compiled from "Hello.java"
public class lime.jvm._002._004._002.Hello
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #18.#29        // java/lang/Object."<init>":()V
   #2 = Class              #30            // lime/jvm/_002/_004/_002/Hello
   #3 = Methodref          #2.#29         // lime/jvm/_002/_004/_002/Hello."<init>":()V
   #4 = Methodref          #2.#31         // lime/jvm/_002/_004/_002/Hello.test:()I
   #5 = Integer            32768
   #6 = Float              15.0f
   #7 = Float              3.0f
   #8 = Double             15.0d
  #10 = Double             3.0d
  #12 = String             #32            // ab
  #13 = Class              #33            // java/lang/StringBuilder
  #14 = Methodref          #13.#29        // java/lang/StringBuilder."<init>":()V
  #15 = Methodref          #13.#34        // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #16 = String             #35            // c
  #17 = Methodref          #13.#36        // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #18 = Class              #37            // java/lang/Object
  #19 = Utf8               <init>
  #20 = Utf8               ()V
  #21 = Utf8               Code
  #22 = Utf8               LineNumberTable
  #23 = Utf8               main
  #24 = Utf8               ([Ljava/lang/String;)V
  #25 = Utf8               test
  #26 = Utf8               ()I
  #27 = Utf8               SourceFile
  #28 = Utf8               Hello.java
  #29 = NameAndType        #19:#20        // "<init>":()V
  #30 = Utf8               lime/jvm/_002/_004/_002/Hello
  #31 = NameAndType        #25:#26        // test:()I
  #32 = Utf8               ab
  #33 = Utf8               java/lang/StringBuilder
  #34 = NameAndType        #38:#39        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #35 = Utf8               c
  #36 = NameAndType        #40:#41        // toString:()Ljava/lang/String;
  #37 = Utf8               java/lang/Object
  #38 = Utf8               append
  #39 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #40 = Utf8               toString
  #41 = Utf8               ()Ljava/lang/String;
{
  public lime.jvm._002._004._002.Hello();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 9: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #2                  // class lime/jvm/_002/_004/_002/Hello
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: invokevirtual #4                  // Method test:()I
        12: istore_2
        13: return
      LineNumberTable:
        line 11: 0
        line 12: 8
        line 13: 13

  public int test();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=13, args_size=1
         0: bipush        15         /**1 15压入操作数的栈顶(编译过程中5+10合并成15,并且由于15在-128-127范围,即用bipush)  压栈**/
         2: istore_1                 /**2  从栈顶弹出并压入局部变量表访问索引为1的Slot                         弹栈入局部变量表**/
         3: iload_1                 /**3  将局部变量表中访问索引为1的Slot重新压入栈顶             局部变量表入栈**/
         4: iconst_3                    /**4  数值3压入操作数的栈顶(范围-1~5,即用指令iconst)                         压栈**/
         5: iadd                    /**5  将栈顶的前两个弹出并进行加法运算后将结果重新压入栈顶                         前两弹栈相加**/
         6: istore_2                /**6   从栈顶弹出并压入局部变量表访问索引为2的Slot         弹栈入局部变量表**/
         7: iload_2                 /**7  将局部变量表中访问索引为2的Slot重新压入栈顶            局部变量表入栈**/
         8: bipush        6         /**8  6压入操作数的栈顶(在-128-127范围,用bipush指令) **/
        10: iadd                    /**9  将栈顶的前两个弹出并进行加法运算后将结果重新压入栈顶                         前两弹栈相加**/
        11: istore_2                /**10  从栈顶弹出并压入局部变量表访问索引为2的Slot                         弹栈入局部变量表**/
        12: iload_2                 /**11  将局部变量表中访问索引为2的Slot重新压入栈顶             局部变量表入栈**/
        13: sipush        128       /**12  128压入操作数的栈顶(在-32768~32767范围,用sipush指令) **/
        16: iadd                    /**13  将栈顶的前两个弹出并进行加法运算后将结果重新压入栈顶                         前两弹栈相加**/
        17: istore_2                /**14  从栈顶弹出并压入局部变量表访问索引为2的Slot                         弹栈入局部变量表**/
        18: iload_2                 /**15  将局部变量表中访问索引为2的Slot重新压入栈顶             局部变量表入栈**/
        19: ldc           #5                  // int 32768          /**16 128压入操作数的栈顶(在-2147483648~2147483647范围,用ldc指令) **/
        21: iadd                        /**17  将栈顶的前两个弹出并进行加法运算后将结果重新压入栈顶                         前两弹栈相加**/
        22: istore_2                    /**18  从栈顶弹出并压入局部变量表访问索引为2的Slot                         弹栈入局部变量表**/
        23: bipush        15            /**19  15压入操作数的栈顶(在-128-127范围,用bipush指令) **/
        25: istore_3                /**20  从栈顶弹出并压入局部变量表访问索引为3的Slot                         弹栈入局部变量表**/
        26: iload_3                 /**21  将局部变量表中访问索引为3的Slot重新压入栈顶             局部变量表入栈**/
        27: iconst_3            /**22  数值3压入操作数的栈顶(范围-1~5,即用指令iconst)                         压栈**/
        28: iadd                /**23  将栈顶的前两个弹出并进行加法运算后将结果重新压入栈顶                         前两弹栈相加**/
        29: i2s                 /**24 把int类型的数据转化为short类型 **/
        30: istore        4     /**25 将int类型值存入局部变量 **/
        32: ldc           #6                  // float 15.0f /** 26 验证float、double、String均是从常量池中取出(均使用了ldc、ldc_w、ldc2_w其中一个)**/
        34: fstore        5
        36: fload         5
        38: ldc           #7                  // float 3.0f
        40: fadd
        41: fstore        6
        43: ldc2_w        #8                  // double 15.0d
        46: dstore        7
        48: dload         7
        50: ldc2_w        #10                 // double 3.0d
        53: dadd
        54: dstore        9
        56: ldc           #12                 // String ab
        58: astore        11
        60: new           #13                 // class java/lang/StringBuilder
        63: dup
        64: invokespecial #14                 // Method java/lang/StringBuilder."<init>":()V
        67: aload         11
        69: invokevirtual #15                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        72: ldc           #16                 // String c
        74: invokevirtual #15                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        77: invokevirtual #17                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        80: astore        12
        82: iload_2
        83: ireturn /**50 返回结果 **/
      LineNumberTable:
        line 18: 0
        line 19: 3
        line 20: 7
        line 21: 12
        line 22: 18
        line 24: 23
        line 25: 26
        line 27: 32
        line 28: 36
        line 30: 43
        line 31: 48
        line 33: 56
        line 34: 60
        line 35: 82
}
SourceFile: "Hello.java"

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值