Java栈和局部变量操作
Java虚拟机是基于栈的机器,几乎所有Java虚拟机的指令都与操作数栈相关。栈操作包括把常量压入操作数栈、执行通用的栈操作、在操作数栈和局部变量之间往返传输值。
1常量入栈操作:
操作码在执行常量入栈操作之前,使用三种方式指明常量的值:常量值隐含包含在操作码内部、常量值在字节码中如同操作数一样跟随在操作码之后,或者从常量池中取出常量。
一些操作码自行指明入栈的常量类型和值,例如,iconst_1操作码告知虚拟机先栈压入一个职位1的int类型数。java虚拟机为经常压入栈的各种不同类型的数据定义了一些这样的操作码。相对于从自己吗流中去除操作数或者指向常量池的指令来说,上述这些指令都是冗余指令,但他们更有效率。因为这些指令在字节码流中仅仅占据了一个字节的空间,他们提高了字节吗的执行效率,并减少了字节码的尺寸。向栈中压入int和float类型值的操作码表如下
1.1常量值隐含包含在操作码内部:
将一个字长的常量压入栈(下表所示的操作码为把int和float类型压入栈,int和float都是一个字长的值,栈中的每一个位置的长度都是一个字长(至少32位宽),因此,每当一个int或者float类型被压入栈时,它豆浆占据一个位置)。
操作码 |
操作数 |
说明 |
---|---|---|
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压入栈 |
将两个字长的常量压入栈(long和double类型值都是64位长度的值,没当一个long或者double类型的值被压入栈时,它都讲占据2个位置)
操作码 |
操作数 |
说明 |
---|---|---|
lconst_0 |
(无) |
将long类型值0压入栈 |
lconst_1 |
(无) |
将long类型值1压入栈 |
dconst_0 |
(无) |
将double类型值0压入栈 |
dconst_1 |
(无) |
将double类型值1压入栈 |
给一个对象引用赋空值时会用到aconst_null指令
将空(null)对象引用压入栈(将一个隐式声明的常量压入栈)
操作码 |
操作数 |
说明 |
---|---|---|
aconst_null |
(无) |
将空(null)对象引用压入栈 |
对象引用的格式取决于具体的java虚拟机是想。一个对象引用可能会莫名其妙地指向垃圾收集堆中的java对象。一个空的对象引用表明一个当前还没有指向任何有效对象的对象引用变量。给一个对象引用变量赋空值的过程中,将会使用aconst_null操作码。
例如下面代码:
public class StackTest {
/**
* @param args
*/
public static void 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."
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类型常量压入栈(这两个操作码用来将证书常量压入栈,该整数常量的值再byte和short数据类型的有效范围之内。这两个操作码通过使用一个紧随在操作码之后的操作数明确指定将要压入栈的常量。紧随操作码的byte或者short类型在压入栈之前被扩展成int类型值。将int类型值压入栈的操作实际上渠道了byte和short类型值压入栈的操作)。
操作码 |
操作数 |
说明 |
---|---|---|
bipush |
一个byte类型的数 |
将byte类型的数转换为int类型的数,然后压入栈 |
sipush |
一个short类型的数 |
将short类型的数转换为int类型的数,然后压入栈 |
1.3
从常量池中取出常量,然后将其压入栈。这些操作码使用表示常量池索引的操作数。java虚拟机通过给定的索引查找相应的常量池入口,决定这些常量的类型和值,把它们压入栈。
操作码 |
操作数 |
说明 |
---|---|---|
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范围的位置。
java源代码中所有的字符串文字最终都作为入口储存于常量池中。如果同一个应用程序的多个类都是用同样的字符串文字,那么此字符串文字将在使用它的所有的类的class文件中出现。例如,如果有是三个类使用了字符串文字"Harumph!",那么这个字符串将分别在这三个class文件的常量池中出现。这些类的方法可以使用ldc或者ldc_w指令来把指向一个有着"Harumph!"值的String对象的引用压入栈
java虚拟机把所有具有相同字符顺序的字符串文字处理为同一个String对象,换句话说,如果多个类使用了同一个字符串文字,比如"Harumph!",java虚拟机将只会创建一个具有"Harumph!"值的String对象来表示所有的字符串文字
例如下面代码:
public class StackTest {
/**
* @param args
*/
public static void 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."
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
}