JVM学习05-字节码执行过程和JVM指令集

1. 字节码的Code部分

在执行字节码的时候,无非也就是对调用类中的函数。那么下面将介绍下字节码函数的Code部分,Code部分的代码一个可以用java自带的命令javap命令进行查看。还可以在eclipse中安装ByteCode visualizer插件查看,具体使用自行研究。
在我介绍java内存模型的时候,函数的执行过程是分配在栈内存中的,所以在执行Code部分的时候肯定会涉及到局部变量表和操作数栈,同时还会涉及到程序计数器。之前也介绍了一个小例子,下面继续先以一个小例子讲述。

2. 例子

源码:

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)。
JVM学习05-字节码执行过程和JVM指令集_第1张图片

2. int b = 2;
跟 int a = 1; 基本一致。
JVM学习05-字节码执行过程和JVM指令集_第2张图片

3. int c = a + b;
这里iload指令(i表示int型,后面会列出常用的指令表)将下标为1和2,即a和b压入栈中。然后执行 idd 指令,弹出两个操作数进行相加,并将结果值压入操作数栈中。最后istore_3指令将栈中的操作数弹出并赋值给变量c。
JVM学习05-字节码执行过程和JVM指令集_第3张图片

JVM学习05-字节码执行过程和JVM指令集_第4张图片

4. return c;
这里先将c的值载入压入到栈中,然后执行ireturn指令。
JVM学习05-字节码执行过程和JVM指令集_第5张图片
这里有一个很经典的面试题,就是try中的return和finally中的return,两个return的执行流程。其实看一下反汇编的指令就一目了然了。

3. JVM指令集

具体的指令集可以参照博文:
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型常量值从常量池中压入到栈中(宽索引)。

3.2 局部变量操作

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指令类似。

3.3 通用栈操作

1. nop
什么都不做。

2. pop
从栈顶弹出一个字长。

3. dup
赋值栈顶一个字长,复制内容压入栈中。

3.4 类型转化

i2c、i2b、i2s、i2l,i2f,l2i,l2f,l2d,f2i,f2d,d2i,d2l,d2f。

3.5 整数运算和浮点运算

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型变量增加指定的值
 

3.6 对象操作指令和方法调用指令

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))

3.7 条件控制

ifeq,ifne,if_icmpeq 等等一些指令。详细见指令表。

你可能感兴趣的:(JVM)