在执行字节码的时候,无非也就是对调用类中的函数。那么下面将介绍下字节码函数的Code部分,Code部分的代码一个可以用java自带的命令javap命令进行查看。还可以在eclipse中安装ByteCode visualizer插件查看,具体使用自行研究。
在我介绍java内存模型的时候,函数的执行过程是分配在栈内存中的,所以在执行Code部分的时候肯定会涉及到局部变量表和操作数栈,同时还会涉及到程序计数器。之前也介绍了一个小例子,下面继续先以一个小例子讲述。
源码:
package com.minosa.test;
public class HelloClass {
public int foo(){
int a = 1;
int b = 2;
int c = a + b;
return c;
}
}
ByteCode visualizer查看
public int foo() {
/* L6 */
0 iconst_1;
1 istore_1; /* a */
/* L7 */
2 iconst_2;
3 istore_2; /* b */
/* L8 */
4 iload_1; /* a */
5 iload_2; /* b */
6 iadd;
7 istore_3; /* c */
/* L9 */
8 iload_3; /* c */
9 ireturn;
/* LineNumberTable */
/* ----------+------------- */
/* start_pc | line_number */
/* ----------+------------- */
/* 0 | 6 */
/* 2 | 7 */
/* 4 | 8 */
/* 8 | 9 */
/* ----------+------------- */
/* LocalVariableTable */
/* -------+----------+--------+------------+------------------ */
/* index | start_pc | length | name_index | descriptor_index */
/* -------+----------+--------+------------+------------------ */
/* 0 | 0 | 10 | 12 | 13 */
/* 1 | 2 | 8 | 16 | 17 */
/* 2 | 4 | 6 | 18 | 17 */
/* 3 | 8 | 2 | 19 | 17 */
/* -------+----------+--------+------------+------------------ */
/* ExceptionTable (empty) */
/* max_stack: 2 max_locals: 4 */
}
上面在字节码的文件结构中讲过,每个方法中存在很多属性,例如LineNumberTable,LocalVariableTable 等等。里面的一些”_index”就是指向常量池中的索引。对于成员方法来说(非类方法),局部变量表中的第一个是this(之前有讲过)。
这里抽出最关键的Code进行讲述:
由上面的LocalVariableTable可以知道,a变量的index是1,b是2,c是3。
public int foo() {
/* L6 */
0 iconst_1;
1 istore_1; /* a */
/* L7 */
2 iconst_2;
3 istore_2; /* b */
/* L8 */
4 iload_1; /* a */
5 iload_2; /* b */
6 iadd;
7 istore_3; /* c */
/* L9 */
8 iload_3; /* c */
9 ireturn;
}
1. int a = 1;
首先程序计数器值为0,iconst_1指令将整数1压入操作数栈中;执行istore_1,程序计数器为1,将操作数栈中的数弹出,然后赋值给索引为1的变量(即a)。
2. int b = 2;
跟 int a = 1; 基本一致。
3. int c = a + b;
这里iload指令(i表示int型,后面会列出常用的指令表)将下标为1和2,即a和b压入栈中。然后执行 idd 指令,弹出两个操作数进行相加,并将结果值压入操作数栈中。最后istore_3指令将栈中的操作数弹出并赋值给变量c。
4. return c;
这里先将c的值载入压入到栈中,然后执行ireturn指令。
这里有一个很经典的面试题,就是try中的return和finally中的return,两个return的执行流程。其实看一下反汇编的指令就一目了然了。
具体的指令集可以参照博文:
http://blog.csdn.net/lm2302293/article/details/6713147
里面描述了指令在字节码中对应的Byte,助记符以及功能描述,下面将对常用进行描述下。
3.1 常量入栈
1. aconst_null
null对象入栈,前面的a表示对象ref。
2. iconst_m1
将 -1 压入栈中。其他的byte型和short型参照 bipush 和 sipush 指令
3. iconst_0 ~ iconst_5
将整数 0 ~ 5 压入栈中。其他的byte型和short型参照 bipush 和 sipush 指令
4. lconst_1 ~ lconst_2
将long类型常量 1或2 压入栈中,其他参照 ldc2_w 指令;
5. fconst_1 ~ fconst_2
将float类型常量 1或2 压入栈中,其他参照 ldc 指令。
6. dconst_1 ~ dconst_1
将double类型常量 1或2 压入栈中,其他参照 ldc2_w 指令。
7. bipush 和 sipush
bipush将一个byte的带符号常量压入栈中,sipush将一个short型带符号常量压入栈中
8. ldc,ldc_w和ldc2_w
ldc 将int、float或String型常量值从常量池中压入栈中。
ldc_w将int、float或String型常量值从常量池中压入栈中(宽索引)。
ldc2_w将long或double型常量值从常量池中压入到栈中(宽索引)。
1. load(为i,l,f,d和a中一个)
分别将int型,long型,float型,double型以及Object ref型的局部变量压入栈中。指令后面继续跟着”0,_1,_2,_3”表示将索引为0 ~ 3的局部变量压入到栈中,大于3的索引则去掉下划线”“,如”iload_1”,”iload 4”。
2. aload(为c,b,s,i,l,f,d和a中一个)
将指定类型数组中的值压入到栈中。取值的时候先将数组的ref压入栈,然后是需要获取数据的index,然后执行*aload指令,并将获取到的值压入到栈中。这里可能会抛出数组下标越界的异常。
3. *store 和 *astore 指令
这里基本和 *load 和 *aload指令类似。
1. nop
什么都不做。
2. pop
从栈顶弹出一个字长。
3. dup
赋值栈顶一个字长,复制内容压入栈中。
i2c、i2b、i2s、i2l,i2f,l2i,l2f,l2d,f2i,f2d,d2i,d2l,d2f。
iadd,ladd,fadd,dadd 加 +
isub,lsub,fsub,dsub 减 -
imul,lmul,fmul,dmul 乘 *
idiv, ldiv,fdiv,ddiv 除 /
irem,lrem,frem,drem 取模 %
ineg,lneg,fneg,dneg 取负 -
ishl,lshl 左移 <<
ishr,lshr,iushr,lushr 有符号和无符号右移 >>和>>>
iand,land 按位与 &
ior,lor 按位或 |
ixor,lxor 按位异或 ^
iinc 指定int型变量增加指定的值
1. new
创建一个对象,并将引用压入栈中。
2. invokespecial 和 invokevirtual
调用构造方法,私有方法
3. invokestatic 和 invokeinterface
调用静态方法和接口方法
4. getstatic 和 putstatic
getstatic 获取指定类的静态域并将值压入栈中;putstatic为指定静态域赋值。
5. getfield 和 putfield
同上,只是针对实例域
6. return (为 i l f d a 或(为空return即void))
ifeq,ifne,if_icmpeq 等等一些指令。详细见指令表。