环境: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"