[JVM]了断局: 局部变量表和操作数栈实例分析

一.前言

本文以两段代码示例来解释说明,JVM在执行类中的方法时,[局部变量表]和[操作数栈]是如何配合工作的.

示例一: 

1.代码

package com.classloading;

public class Circumference {

    public static void main(String[] args) {
        Circumference.circumference(10) ;
    }

    public static double circumference(float r) {
        float pi = 3.14f;
        float area = 2 * pi * r;
        return area;
    }
}

 

2.字节码

bogon:classloading sysadmin$ javap -v Circumference.class 
Classfile /workspace/java-basis/target/classes/com/classloading/Circumference.class
  Last modified 2020-8-26; size 585 bytes
  MD5 checksum 848df06176b0272e2c0fd4426a8135f5
  Compiled from "Circumference.java"
public class com.classloading.Circumference
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#26         // java/lang/Object."":()V
   #2 = Float              10.0f
   #3 = Methodref          #5.#27         // com/classloading/Circumference.circumference:(F)D
   #4 = Float              3.14f
   #5 = Class              #28            // com/classloading/Circumference
   #6 = Class              #29            // java/lang/Object
   #7 = Utf8               
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/classloading/Circumference;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               circumference
  #19 = Utf8               (F)D
  #20 = Utf8               r
  #21 = Utf8               F
  #22 = Utf8               pi
  #23 = Utf8               area
  #24 = Utf8               SourceFile
  #25 = Utf8               Circumference.java
  #26 = NameAndType        #7:#8          // "":()V
  #27 = NameAndType        #18:#19        // circumference:(F)D
  #28 = Utf8               com/classloading/Circumference
  #29 = Utf8               java/lang/Object
{
  public com.classloading.Circumference();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/classloading/Circumference;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: ldc           #2                  // float 10.0f
         2: invokestatic  #3                  // Method circumference:(F)D
         5: pop2
         6: return
      LineNumberTable:
        line 6: 0
        line 7: 6
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  args   [Ljava/lang/String;

  public static double circumference(float);
    descriptor: (F)D
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #4                  // float 3.14f
         2: fstore_1
         3: fconst_2
         4: fload_1
         5: fmul
         6: fload_0
         7: fmul
         8: fstore_2
         9: fload_2
        10: f2d
        11: dreturn
      LineNumberTable:
        line 10: 0
        line 11: 3
        line 12: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      12     0     r   F
            3       9     1    pi   F
            9       3     2  area   F
}
SourceFile: "Circumference.java"

 

3.解析.

我们主要关注circumference 方法,该方法对应的字节码片段如下:

bogon:classloading sysadmin$ javap -v Circumference.class 
Classfile /workspace/java-basis/target/classes/com/classloading/Circumference.class
  Last modified 2020-8-26; size 585 bytes
  MD5 checksum 848df06176b0272e2c0fd4426a8135f5
  Compiled from "Circumference.java"
public class com.classloading.Circumference
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#26         // java/lang/Object."":()V
   #2 = Float              10.0f
   #3 = Methodref          #5.#27         // com/classloading/Circumference.circumference:(F)D
   #4 = Float              3.14f
   #5 = Class              #28            // com/classloading/Circumference
   #6 = Class              #29            // java/lang/Object
   #7 = Utf8               
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/classloading/Circumference;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               circumference
  #19 = Utf8               (F)D
  #20 = Utf8               r
  #21 = Utf8               F
  #22 = Utf8               pi
  #23 = Utf8               area
  #24 = Utf8               SourceFile
  #25 = Utf8               Circumference.java
  #26 = NameAndType        #7:#8          // "":()V
  #27 = NameAndType        #18:#19        // circumference:(F)D
  #28 = Utf8               com/classloading/Circumference
  #29 = Utf8               java/lang/Object
{
  public com.classloading.Circumference();
    #描述符: 无入参, 返回值为空
    descriptor: ()V
    #访问权限:  public
    flags: ACC_PUBLIC
    #代码
    Code:
      #栈深度为1, 本地变量为1个, 入参为1个.
      stack=1, locals=1, args_size=1
         # 将第一个引用类型本地变量推送至栈顶
         0: aload_0
         #调用超类构建方法, 实例初始化方法, 私有方法
         1: invokespecial #1                  // Method java/lang/Object."":()V
         # 从当前方法返回void
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/classloading/Circumference;
  
  # main 方法入口
  public static void main(java.lang.String[]);
    #描述符: 方法入参是一个 String类型的数组, 返回值为空
    descriptor: ([Ljava/lang/String;)V
    #访问权限:  public  static 方法
    flags: ACC_PUBLIC, ACC_STATIC
    #代码
    Code:
      #栈深度为2, 本地变量为1个, 入参为1个.
      stack=2, locals=1, args_size=1
         # 将float类型常量值[坐标 #2 10.0f]从常量池中推送至栈顶
         0: ldc           #2                  // float 10.0f
         # 调用静态方法 [ 常量池 坐标 #3 Method circumference:(F)D  ]
         2: invokestatic  #3                  // Method circumference:(F)D
         # 将栈顶的一个(对于非long或double类型)或两个数值(对于非long或double的其他类型)弹出
         5: pop2
         # 从当前方法返回void
         6: return
      LineNumberTable:
        line 6: 0
        line 7: 6
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  args   [Ljava/lang/String;

  #circumference 方法
  public static double circumference(float);
    #描述符 : 入参为 float 类型, 返回值为Double类型
    descriptor: (F)D
    #访问权限:  public  static 方法
    flags: ACC_PUBLIC, ACC_STATIC
    #代码
    Code:
      #栈深度为2, 本地变量有三个, 输入参数1个
      stack=2, locals=3, args_size=1
         # 将float类型常量值[坐标  #4  3.14f ]从常量池中推送至栈顶.
         0: ldc           #4                  // float 3.14f
         # 将栈顶float型数值存入第二个本地变量
         2: fstore_1
         # 将float型2推送至栈顶
         3: fconst_2 
         # 将第二个float型本地变量推送至栈顶
         4: fload_1
         # 将栈顶两float型数值相乘并将结果压入栈顶
         5: fmul
         # 将第一个float型本地变量推送至栈顶
         6: fload_0
         # 将栈顶两float型数值相乘并将结果压入栈顶
         7: fmul
         # 将栈顶float型数值存入第三个本地变量
         8: fstore_2
         # 将第三个float型本地变量推送至栈顶
         9: fload_2
         # 将栈顶float型数值强制转换为double型数值并将结果压入栈顶
        10: f2d
        #  从当前方法返回double
        11: dreturn
      LineNumberTable:
        line 10: 0
        line 11: 3
        line 12: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      12     0     r   F
            3       9     1    pi   F
            9       3     2  area   F

 

指令含义可以通过这篇文章来进行查询: [JVM]了断局: 虚拟机字节码指令表速查

 

4.画图演示

 

[JVM]了断局: 局部变量表和操作数栈实例分析_第1张图片

 

示例二:
1.代码

package com.classloading;

public class Calc {

    public static void main(String[] args) {
        new Calc().calc();
    }

    public int calc() {
        int a = 100;
        int b = 200;
        int c = 300;
        return (a + b) * c;
    }

}


2.字节码

bogon:classloading sysadmin$ javap -v Calc.class 
Classfile /workspace/java-basis/target/classes/com/classloading/Calc.class
  Last modified 2020-8-26; size 563 bytes
  MD5 checksum bfe5bf36c453f085c6dd0a44c7d7e2ee
  Compiled from "Calc.java"
public class com.classloading.Calc
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#25         // java/lang/Object."":()V
   #2 = Class              #26            // com/classloading/Calc
   #3 = Methodref          #2.#25         // com/classloading/Calc."":()V
   #4 = Methodref          #2.#27         // com/classloading/Calc.calc:()I
   #5 = Class              #28            // java/lang/Object
   #6 = Utf8               
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               LocalVariableTable
  #11 = Utf8               this
  #12 = Utf8               Lcom/classloading/Calc;
  #13 = Utf8               main
  #14 = Utf8               ([Ljava/lang/String;)V
  #15 = Utf8               args
  #16 = Utf8               [Ljava/lang/String;
  #17 = Utf8               calc
  #18 = Utf8               ()I
  #19 = Utf8               a
  #20 = Utf8               I
  #21 = Utf8               b
  #22 = Utf8               c
  #23 = Utf8               SourceFile
  #24 = Utf8               Calc.java
  #25 = NameAndType        #6:#7          // "":()V
  #26 = Utf8               com/classloading/Calc
  #27 = NameAndType        #17:#18        // calc:()I
  #28 = Utf8               java/lang/Object
{
  public com.classloading.Calc();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/classloading/Calc;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: new           #2                  // class com/classloading/Calc
         3: dup
         4: invokespecial #3                  // Method "":()V
         7: invokevirtual #4                  // Method calc:()I
        10: pop
        11: return
      LineNumberTable:
        line 6: 0
        line 7: 11
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      12     0  args   [Ljava/lang/String;

  public int calc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: bipush        100
         2: istore_1
         3: sipush        200
         6: istore_2
         7: sipush        300
        10: istore_3
        11: iload_1
        12: iload_2
        13: iadd
        14: iload_3
        15: imul
        16: ireturn
      LineNumberTable:
        line 10: 0
        line 11: 3
        line 12: 7
        line 13: 11
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      17     0  this   Lcom/classloading/Calc;
            3      14     1     a   I
            7      10     2     b   I
           11       6     3     c   I
}
SourceFile: "Calc.java"
bogon:classloading sysadmin$ 


3.解析
 

bogon:classloading sysadmin$ javap -v Calc.class 
Classfile /workspace/java-basis/target/classes/com/classloading/Calc.class
  Last modified 2020-8-26; size 563 bytes
  MD5 checksum bfe5bf36c453f085c6dd0a44c7d7e2ee
  Compiled from "Calc.java"
public class com.classloading.Calc
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#25         // java/lang/Object."":()V
   #2 = Class              #26            // com/classloading/Calc
   #3 = Methodref          #2.#25         // com/classloading/Calc."":()V
   #4 = Methodref          #2.#27         // com/classloading/Calc.calc:()I
   #5 = Class              #28            // java/lang/Object
   #6 = Utf8               
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               LocalVariableTable
  #11 = Utf8               this
  #12 = Utf8               Lcom/classloading/Calc;
  #13 = Utf8               main
  #14 = Utf8               ([Ljava/lang/String;)V
  #15 = Utf8               args
  #16 = Utf8               [Ljava/lang/String;
  #17 = Utf8               calc
  #18 = Utf8               ()I
  #19 = Utf8               a
  #20 = Utf8               I
  #21 = Utf8               b
  #22 = Utf8               c
  #23 = Utf8               SourceFile
  #24 = Utf8               Calc.java
  #25 = NameAndType        #6:#7          // "":()V
  #26 = Utf8               com/classloading/Calc
  #27 = NameAndType        #17:#18        // calc:()I
  #28 = Utf8               java/lang/Object
{
  public com.classloading.Calc();
    #描述符: 无入参, 返回值为空
    descriptor: ()V
    #访问权限:  public
    flags: ACC_PUBLIC
    #代码
    Code:
      #栈深度为1, 本地变量为1个, 入参为1个.
      stack=1, locals=1, args_size=1
         # 将第一个引用类型本地变量推送至栈顶
         0: aload_0
         #调用超类构建方法, 实例初始化方法, 私有方法
         1: invokespecial #1                  // Method java/lang/Object."":()V
         # 从当前方法返回void
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/classloading/Calc;
  # main 方法入口
  public static void main(java.lang.String[]);
    描述符: 方法入参是一个 String类型的数组, 返回值为空
    descriptor: ([Ljava/lang/String;)V
    #访问权限:  public  static 方法
    flags: ACC_PUBLIC, ACC_STATIC
    #代码
    Code:
      #栈深度为2, 本地变量为1个, 入参为1个.
      stack=2, locals=1, args_size=1
         # 创建一个对象, 并将其引用引用值压入栈顶
         0: new           #2                  // class com/classloading/Calc
         # 复制栈顶数值并将复制值压入栈顶
         3: dup
         #调用超类构建方法, 实例初始化方法, 私有方法
         4: invokespecial #3                  // Method "":()V
         #调用实例方法
         7: invokevirtual #4                  // Method calc:()I
         # 将栈顶数值弹出(数值不能是long或double类型的)
        10: pop
        #从当前方法返回void
        11: return
      LineNumberTable:
        line 6: 0
        line 7: 11
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      12     0  args   [Ljava/lang/String;

  public int calc();
    描述符: 无入参, 返回值为Int类型
    descriptor: ()I
    #访问权限:  public
    flags: ACC_PUBLIC
    #代码
    Code:
      #栈深度为2, 本地变量有四个, 输入参数1个
      stack=2, locals=4, args_size=1
         # 将单字节的常量值 [100] 推送至栈顶
         0: bipush        100
         # 将栈顶int型数值存入第二个本地变量
         2: istore_1
         # 将一个短整型常量200推送至栈顶
         3: sipush        200
         # 将栈顶int型数值存入第三个本地变量
         6: istore_2
         # 将一个短整型常量300推送至栈顶
         7: sipush        300
         # 将栈顶int型数值存入第四个本地变量
        10: istore_3
        # 将第二个int型本地变量推送至栈顶
        11: iload_1
        # 将第三个int型本地变量推送至栈顶
        12: iload_2
        # 将栈顶两int型数值相加并将结果压入栈顶
        13: iadd
        # 将第四个int型本地变量推送至栈顶
        14: iload_3
        # 将栈顶两int型数值相乘并将结果压入栈顶
        15: imul
        # 从当前方法返回int
        16: ireturn
      LineNumberTable:
        line 10: 0
        line 11: 3
        line 12: 7
        line 13: 11
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      17     0  this   Lcom/classloading/Calc;
            3      14     1     a   I
            7      10     2     b   I
           11       6     3     c   I
}
SourceFile: "Calc.java"
bogon:classloading sysadmin$ 

 


4.画图演示

[JVM]了断局: 局部变量表和操作数栈实例分析_第2张图片

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:([JVM]了断局)