本文以两段代码示例来解释说明,JVM在执行类中的方法时,[局部变量表]和[操作数栈]是如何配合工作的.
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;
}
}
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"
我们主要关注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]了断局: 虚拟机字节码指令表速查
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;
}
}
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$
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$