标签:
原文:http://ayufox.iteye.com/blog/672269
理解JVM的指令的一个基础是理解JVM的栈内存,因此在开始之前最好先参阅一下《Java 栈内存介绍 》。本篇将结合例子对JVM的主要指令进行描述。
在开始之前,我们先了解一下如下的 “常识”:
1.主要运算指令
1)常量操作指令
可能会很奇怪地发现,JVM使用了前缀I、F、A这种实际效果差不多的不同指令,实际上其目的是为了JVM在进行字节码验证的时候更好地检查类型。
2)Local变量操作指令
与上类似的,JVM使用了前缀I、F、A这种实际效果差不多的不同指令,实际上其目的是为了JVM在进行字节码验证的时候更好地检查类型。
3)栈操作指令
4)运算指令
运算指令非常简单,单指令,没有操作数,操作的参数放在栈中,运算的时候都是从栈顶弹出2个参数(对于D开头的指令,每个参数是2个字,则会弹出2*2个字的信息),运算完后把结果入栈顶(根据类型不同,结果会占用1-2个字),更详细可以看看《Java 栈内存介绍 》中的范例
5)类型转换指令
类型转换指令,非常简单,看后面例子就能明白
6)范例:
下面的例子演示了如上的大部分类型的字节码的功能
public long compute() { short i = 80; int j = 1000; long result = i * j + 1000000; return result; }
public long compute(); Code: Stack=2, Locals=5, Args_size=1 //声明了栈的最大深度、本地字数和传入参数数,对于对象方法,会传入this引用,因此这里Arg_szie=1,如上的程序,this会占用1个字,i和j分别占1个字,result占2个字(long),因此这里Locals=5 0: bipush 80 //将80入到栈,在栈中会占1个字的位置 2: istore_1 //将栈顶值弹出设给第2个本地变量(传入参数也会以本地变量的方式存在,在这了第1个参数是this),这两段指令等价于short i = 80,从这里可以看出,JVM直接把short当做integer来运算的 3: sipush 1000 //与上类似,把1000入到栈顶,这里1000超过了b所能表示的范围,所以是sipush 6: istore_2 //同样的,把堆栈值弹出并设给第3个本地变量,这两段等价于int j = 1000 7: iload_1 8: iload_2 //把第2个本地变量(i和j)入栈 9: imul //乘运算,弹出2个栈顶值(i和j),并把运算结果入栈,这时候栈顶值就是 i* j 10: ldc #16; //1000000超过short能够表示的范围,会以常量池中条目的形式存在,这里#16就是1000000,这里把1000000入栈 12: iadd //弹出栈顶值2个字的值,并进行add操作,把add结果再入栈,这时i*j和1000000被弹出栈,并把i*j+1000000的值入栈 13: i2l //从栈顶弹出1个字的值,并转换成l型,再入到栈中(这时候,i*j+1000000会占用栈顶2个字的位置。 14: lstore_3 //从栈顶弹出2个字(因为是l型的),并把结果赋给第4和第5个local位置(l需要占2个位置),想当于把运算结果赋给result 15: lload_3 //将第4和第5个local位置的值入栈 16: lreturn //返回指令,将栈顶2个位置的值弹出,并压入方法调用者的操作栈(上一个方法的操作栈),同时把本方法的操作栈清空
2.操作指令
1)数组操作指令:
下面我们看一个演示例子
public void test(){ //单维数组 int[] iarray = new int[10]; iarray[3] = 10; int length = iarray.length; int result = iarray[3]; //对象数组 Object[] objs = new Object[10]; }
public void test(); Code: Stack=3, Locals=6, Args_size=1 0: bipush 10 //将数组长度入栈 2: newarray int //创建int[10],并将数组引用入栈 4: astore_1 //将创建的数组的引用出栈,赋给第2个本地变量,即iarray 5: aload_1 //将iarray入栈 6: iconst_3 //数组下标是3 7: bipush 10 //值是10 9: iastore //设置iarray[3] = 10,并将3个值出栈 10: aload_1 //将iarray入栈 11: arraylength //将iarray出栈,获得数组长度,并将长度值入栈 12: istore_2 //将数组长度值出栈,并赋给第3个本地变量,即length 13: aload_1 //将iarray入栈 14: iconst_3 //数组下标是3 15: iaload //将如上2个参数出栈,并将iarray[3]的值入对栈 16: istore_3 //将栈顶值(即iarray[3])出栈,并赋给第4个本地变量,即使result 17: bipush 10 19: anewarray #3; //class java/lang/Object,创建Object数组 22: astore 4 24: return
2)对象相关指令
我们来看一个范例
private int age; public void test(){ Object obj = new Object(); synchronized (obj) { int result = age; } }
public void test(); Code: Stack=2, Locals=4, Args_size=1 0: new #3; //class java/lang/Object //创建Object对象,并将引用入栈 3: dup 4: invokespecial #10; //Method java/lang/Object."<init>":()V //调用Object对象的构造函数,因为方法调用会弹出参数(这里是Object对象),因此需要上面的dup指令,保证在调用构造函数之后栈顶上还是 Object对象的引用,很多种情况下dup指令都是为这个目的而存在的 7: astore_1 8: aload_1 9: dup 10: astore_2 11: monitorenter //进入Object对象的锁,这里会弹出Object的引用,因此需要注意保存锁对象引用本身 12: aload_0 13: getfield #17; //Field age:I //读age属性,注意,这里可能会抛出异常,这里需要确保进入Object对象的锁后准确地在退出的时候调用monitorexit,看后面的异常表 16: istore_3 17: aload_2 18: monitorexit 19: goto 25 22: aload_2 23: monitorexit 24: athrow 25: return Exception table: from to target type 12 19 22 any 22 24 22 any
3)跳转指令
我们看看例子来理解一下这些跳转指令
public int test(int i){ if (i > 100) { return 200; } //case语句比较连续,会翻译成tableswitch switch (i){ case 1: return 1; case 2: return 2; } //case语句不连续,会翻译成lookupswitch switch (i){ case 1: return 1; case 100: return 100; } return 0; }
public int test(int); Code: Stack=2, Locals=2, Args_size=2 0: iload_1 //将第2个参数入栈,即i 1: bipush 100 //将100入栈 3: if_icmple 10 //如果i<=100,则跳转到第10条语句 6: sipush 200 9: ireturn //返回200 10: iload_1 //将第2个参数入栈,即i 11: tableswitch{ //1 to 2 1: 32; 2: 34; default: 36 } //case语句比较连续,使用tableswitch 32: iconst_1 33: ireturn 34: iconst_2 35: ireturn 36: iload_1 37: lookupswitch{ //2 1: 64; 100: 66; default: 69 } //case语句不连续,使用lookupswitch 64: iconst_1 65: ireturn 66: bipush 100 68: ireturn 69: iconst_0 70: ireturn
4)返回指令
返回指令没什么好说,上面几个例子都会涉及到
5)指令quick版本
在上面指令中,有很多指令是需要涉及到引用解析的,譬如NEW、LDC、NEWARRAY等,虽然整个过程只需要一次解析,但每次都需要去判断需不需要解析。为了避免这种无谓的判断,Sun JVM在实现上对这些指令有一个快速的指令版本,在运行的时候,如果已经引用已经解析过了,则把相应的指令替换快速的指令版本,快速引用会直接使用解析后的结果。
2.其他相关
1)Exception表
异常的支持是JVM级别的,对于每个方法,class文件中会携带该方法的异常和处理handler信息,如下,第1行表示代码在执行0-8行的时候,如果有java.lang.Exception抛出,则跳转到11条指令,第2行表示代码在执行0-20行的时候,如果有异常抛出(不管什么类型的异常),则跳转到第29条指令
Exceptions: [0-8): 11 - java.lang.Exception [0-20): 29
关于异常更多信息,可参见《[字节码系列]ObjectWeb ASM构建Method Monitor 》
2)LineNumber表
LineNumber存储了字节码与源码的行数的对应关系,其存在是为了支持Exception或者调试的时候,可以正确地获得代码所在的行数,如下
LineNumberTable: line 8: 0 line 9: 8 line 11: 12 line 9: 17 line 13: 25
这个表不是必要的,有些编译器或者选项会选择不编译到class文件中
3)LocalVariable表
LocalVariable存储了本地变量在源码中的实际名字,如下
LocalVariableTable: Start Length Slot Name Signature 0 25 0 this Ltest/Test3; 5 20 1 iarray [I 13 12 2 length I 17 8 3 result I 24 1 4 objs [Ljava/lang/Object;
这个表不是必要的,有些编译器或者选项会选择不编译到class文件中
标签:
原文地址:http://www.cnblogs.com/winner-0715/p/4998217.html