Java栈和局部变量操作
Java虚拟机是基于栈的机器,几乎所有Java虚拟机的指令都与操作数栈相关。栈操作包括把常量压入操作数栈、执行通用的栈操作、在操作数栈和局部变量之间往返传输值。
1常量入栈操作:
操作码在执行常量入栈操作之前,使用三种方式指明常量的值:常量值隐含包含在操作码内部、常量值在字节码中如同操作数一样跟随在操作码之后,或者从常量池中取出常量。
1.1常量值隐含包含在操作码内部:
将一个字长的常量压入栈
操作码 | 操作数 | 说明 |
iconst_m1 | (无) | 将int类型值-1压入栈 |
iconst_0 | (无) | 将int类型值0压入栈 |
iconst_1 | (无) | 将int类型值1压入栈 |
iconst_2 | (无) | 将int类型值2压入栈 |
iconst_3 | (无) | 将int类型值3压入栈 |
iconst_4 | (无) | 将int类型值4压入栈 |
iconst_5 | (无) | 将int类型值5压入栈 |
fconst_0 | (无) | 将float类型值0压入栈 |
fconst_1 | (无) | 将float类型值1压入栈 |
fconst_2 | (无) | 将float类型值2压入栈 |
将两个字长的常量压入栈
操作码 | 操作数 | 说明 |
lconst_0 | (无) | 将long类型值0压入栈 |
lconst_1 | (无) | 将long类型值1压入栈 |
dconst_0 | (无) | 将double类型值0压入栈 |
dconst_1 | (无) | 将double类型值1压入栈 |
给一个对象引用赋空值时会用到aconst_null指令
将空(null)对象引用压入栈
操作码 | 操作数 | 说明 |
aconst_null | (无) | 将空(null)对象引用压入栈 |
例如下面代码:
publicclass
StackTest {
/**
* @param
args
*/
publicstaticvoid
main(String[] args) {
//
TODO
Auto-generated method stub
int
i = 0;
int
j = 4;
int
k;
k
= i + j;
float
a = 0;
float
b = 1;
float
c =
a + b;
long
x = 0;
long
y = 1;
long
z =
x + y;
String
string
= null;
}
}
用javap工具查看其字节码为:
Compiled
from "StackTest.java"
public
class StackTest extends java.lang.Object{
public
StackTest();
Code:
0: aload_0
1: invokespecial #8;
//Method java/lang/Object."<init>":()V
4: return
public
static void main(java.lang.String[]);
Code:
0: iconst_0
//常量int类型的0入栈
1: istore_1
//弹出栈顶元素0存入位置1的局部变量中
2: iconst_4
//常量int类型的4入栈
3: istore_2
//弹出栈顶元素4存入位置2的局部变量中
4: iload_1
//从位置为1的局部变量中取出元素int类型的0压入栈
5: iload_2
//从位置为2的局部变量中取出元素int类型的4压入栈
6: iadd
//从栈顶弹出两个元素然后做加法,把结果压入栈
7: istore_3
//弹出栈顶元素4存入位置为3的局部变量中
8: fconst_0
//常量float类型的0入栈
9: fstore 4
//弹出栈顶元素0存入位置为4的局部变量中
11: fconst_1
//常量float类型的1入栈
12: fstore 5
//弹出栈顶元素1存入位置为5的局部变量中
14: fload 4
//从位置为4的局部变量中取出元素float类型的0压入栈
16: fload 5
//从位置为5的局部变量中取出元素float类型的1压入栈
18: fadd
//从栈顶弹出两个元素然后做加法,把结果压入栈
19: fstore 6
//弹出栈顶元素1存入位置为3的局部变量中
21: lconst_0
//常量long类型的0入栈
22: lstore 7
// 弹出栈顶元素0存入位置为7和8的局部变量中
24: lconst_1
//常量long类型的1入栈
25: lstore 9
// 弹出栈顶元素0存入位置为9和10的局部变量中
27: lload 7
//从位置为7和8的局部变量中取出元素long类型的0压入栈
29: lload 9
//从位置为9和10的局部变量中取出元素long类型的1压入栈
31: ladd
//从栈顶弹出两个元素然后做加法,把结果压入栈
32: lstore 11
//弹出栈顶元素1存入位置为11和12的局部变量中
34: aconst_null
//将null对象引用压入栈
35: astore 13
//弹出栈顶元素null存入位置为13的局部变量中
37: return
}
1.2常量值在字节码中跟随在操作码之后:
将byte和short类型常量压入栈
操作码 | 操作数 | 说明 |
bipush | 一个byte类型的数 | 将byte类型的数转换为int类型的数,然后压入栈 |
sipush | 一个short类型的数 | 将short类型的数转换为int类型的数,然后压入栈 |
1.3从常量池中取出常量
操作码 | 操作数 | 说明 |
ldc | 无符号8位数indexbyte | 从由indexbyte指向的常量池入口中取出一个字长的值,然后将其压入栈 |
ldc_w | 无符号16位数indexshort | 从由indexshort指向的常量池入口中取出一个字长的值,然后将其压入栈 |
ldc2_w | 无符号16位数indexshort | 从由indexshort指向的常量池入口中取出两个字长的值,然后将其压入栈 |
这三个操作码是从常量池中取出常量,然后将其压入栈,这些操作码的操作码表示常量池索引,Java虚拟机通过给定的索引查找相应的常量池入口,决定这些常量的类型和值,并把它们压入栈。
常量池索引是一个无符号值,ldc和ldc_w是把一个字长的项压入栈,区别在于:ldc的索引只有一个8位,只能指向常量池中1~255范围的位置。ldc_w的索引有16位,可以指向1~65535范围的位置。
例如下面代码:
publicclass
StackTest {
/**
* @param
args
*/
publicstaticvoid
main(String[] args) {
//
TODO
Auto-generated method stub
byte
i = 125;
byte
j = -128;
int
k =
i + j;
short
a = 32767;
short
b = - 32768;
int
c =
a + b;
int
x = 2147483647;
int
y = -2147483648;
int
z =
x + y;
long
I = 2147483648L;
long
J = -2147483649L;
long
K =
I + J;
}
}
用javap工具查看其字节码为:
Compiled
from "StackTest.java"
public
class StackTest extends java.lang.Object{
public
StackTest();
Code:
0: aload_0
1: invokespecial #8;
//Method java/lang/Object."<init>":()V
4: return
public
static void main(java.lang.String[]);
Code:
0: bipush 125
//将byte类型的255转换成int类型压入栈
2: istore_1
//弹出栈顶元素255存入位置为1的局部变量中
3: bipush -128
//将byte类型的-128转换成int类型压入栈
5: istore_2
//弹出栈顶元素-128存入位置为2的局部变量中
6: iload_1
//取出位置为1的局部变量中的数压入栈
7: iload_2
//取出位置为2的局部变量中的数压入栈
8: iadd
//从栈顶弹出两个元素然后做加法,把结果压入栈
9: istore_3
//弹出栈顶元素存入位置为3的局部变量中
10: sipush 32767
//将short类型的32767转换成int类型压入栈
13: istore 4
//弹出栈顶元素32767存入位置为4的局部变量中
15: sipush -32768
/将short类型的-32768转换成int类型压入栈
18: istore 5
//弹出栈顶元素-32768存入位置为5的局部变量中
20: iload 4
//取出位置为4的局部变量中的数压入栈
22: iload 5
//取出位置为5的局部变量中的数压入栈
24: iadd
//从栈顶弹出两个元素然后做加法,把结果压入栈
25: istore 6
/弹出栈顶元素存入位置为6的局部变量中
27: ldc #16;
//int 2147483647
//从常量池索引16的位置取出2147483647压入栈
29: istore 7
//弹出栈顶元素2147483647存入位置为4的局部变量中
31: ldc #17;
//int -2147483648
//从常量池索引17的位置取出-2147483648压入栈
33: istore 8
//弹出栈顶元素-2147483648存入位置为8的局部变量中
35: iload 7
//取出位置为7的局部变量中的数压入栈
37: iload 8
//取出位置为8的局部变量中的数压入栈
39: iadd
//从栈顶弹出两个元素然后做加法,把结果压入栈
40: istore 9
//弹出栈顶元素存入位置为9的局部变量中
42: ldc2_w #18;
//long 2147483648l
//从常量池索引18的位置取出long类型的2147483648L压入栈
45: lstore 10
//弹出栈顶元素2147483648L存入位置为10和11的局部变量中
47: ldc2_w #20;
//long -2147483649l
//从常量池索引20的位置取出long类型的-2147483649L压入栈
50: lstore 12
//弹出栈顶元素-2147483649L存入位置为12和13的局部变量中
52: lload 10
//取出位置为10和11的局部变量中的数压入栈
54: lload 12
//取出位置为12和13的局部变量中的数压入栈
56: ladd
//从栈顶弹出两个元素然后做加法,把结果压入栈
57: lstore 14
//弹出栈顶元素存入位置为14和15的局部变量中
59: return
}