分享

深入了解Bytecode

 hmtomyang 2012-03-30
二、Bytecode

1,什么是Bytecode
C/C++编译器把源代码编译成汇编代码,Java编译器把Java源代码编译成字节码bytecode。
Java跨平台其实就是基于相同的bytecode规范做不同平台的虚拟机,我们的Java程序编译成bytecode后就可以在不同平台跑了。
.net框架有IL(intermediate language),汇编是C/C++程序的中间表达方式,而bytecode可以说是Java平台的中间语言。
了解Java字节码知识对debugging、performance tuning以及做一些高级语言扩展或框架很有帮助。

2,使用javap生成Bytecode
JDK自带的javap.exe文件可以反汇编Bytecode,让我们看个例子:
Test.java:
Java代码 复制代码 收藏代码
  1. public class Test {  
  2.   public static void main(String[] args) {  
  3.     int i = 10000;  
  4.     System.out.println("Hello Bytecode! Number = " + i);  
  5.   }  
  6. }  

编译后的Test.class:
Java代码 复制代码 收藏代码
  1. 漱壕   1 +  
  2.           
  3.      
  4.     
  5.     
  6.     
  7.      <init> ()V Code LineNumberTable main ([Ljava/lang/String;)V   
  8. SourceFile   Test.java      
  9.     ! " java/lang/StringBuilder Hello Bytecode! Number =   # $  # %  & ' (  ) * Test java/lang/Object java/lang/System out Ljava/io/PrintStream; append -(Ljava/lang/String;)Ljava/lang/StringBuilder; (I)Ljava/lang/StringBuilder; toString ()Ljava/lang/String; java/io/PrintStream println (Ljava/lang/String;)V !   
  10.              
  11.           *                      >     '<  Y                              

使用javap -c Test > Test.bytecode生成的Test.bytecode:
Java代码 复制代码 收藏代码
  1. Compiled from "Test.java"  
  2. public class Test extends java.lang.Object{  
  3. public Test();  
  4.   Code:  
  5.    0:  aload_0  
  6.    1:  invokespecial  #1//Method java/lang/Object."<init>":()V  
  7.    4:  return  
  8.   
  9. public static void main(java.lang.String[]);  
  10.   Code:  
  11.    0:  sipush  10000  
  12.    3:  istore_1  
  13.    4:  getstatic  #2//Field java/lang/System.out:Ljava/io/PrintStream;  
  14.    7:  new  #3//class java/lang/StringBuilder  
  15.    10:  dup  
  16.    11:  invokespecial  #4//Method java/lang/StringBuilder."<init>":()V  
  17.    14:  ldc  #5//String Hello Bytecode! Number =   
  18.    16:  invokevirtual  #6//Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;  
  19.    19:  iload_1  
  20.    20:  invokevirtual  #7//Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;  
  21.    23:  invokevirtual  #8//Method java/lang/StringBuilder.toString:()Ljava/lang/String;  
  22.    26:  invokevirtual  #9//Method java/io/PrintStream.println:(Ljava/lang/String;)V  
  23.    29:  return  
  24.   
  25. }  

JVM就是一个基于stack的机器,每个thread拥有一个存储着一些frames的JVM stack,每次调用一个方法时生成一个frame。
一个frame包括一个local variables数组(本地变量表),一个Operand LIFO stack和运行时常量池的一个引用。

我们来简单分析一下生成的字节码指令:
aload和iload指令的“a”前缀和“i”分别表示对象引用和int类型,其他还有“b”表示byte,“c”表示char,“d”表示double等等
我们这里的aload_0表示将把local variable table中index 0的值push到Operand stack,iload_1类似
invokespecial表示初始化对象,return表示返回
sipush表示把10000这个int值push到Operand stack
getstatic表示取静态域
invokevirtual表示调用一些实例方法
这些指令又称为opcode,Java一直以来只有约202個Opcode,具体请参考Java Bytecode规范。

我们看到Test.class文件不全是二进制的指令,有些是我们可以识别的字符,这是因为有些包名、类名和常量字符串没有编译成二进制Bytecode指令。

3,体验字节码增强的魔力
我们J2EE常用的Hibernate、Spring都用到了动态字节码修改来改变类的行为。
让我们通过看看ASM的org.objectweb.asm.MethodWriter类的部分方法来理解ASM是如何修改字节码的:
Java代码 复制代码 收藏代码
  1. class MethodWriter implements MethodVisitor {  
  2.   
  3.     private ByteVector code = new ByteVector();  
  4.   
  5.     public void visitIntInsn(final int opcode, final int operand) {  
  6.         // Label currentBlock = this.currentBlock;  
  7.         if (currentBlock != null) {  
  8.             if (compute == FRAMES) {  
  9.                 currentBlock.frame.execute(opcode, operand, nullnull);  
  10.             } else if (opcode != Opcodes.NEWARRAY) {  
  11.                 // updates current and max stack sizes only for NEWARRAY  
  12.                 // (stack size variation = 0 for BIPUSH or SIPUSH)  
  13.                 int size = stackSize + 1;  
  14.                 if (size > maxStackSize) {  
  15.                     maxStackSize = size;  
  16.                 }  
  17.                 stackSize = size;  
  18.             }  
  19.         }  
  20.         // adds the instruction to the bytecode of the method  
  21.         if (opcode == Opcodes.SIPUSH) {  
  22.             code.put12(opcode, operand);  
  23.         } else { // BIPUSH or NEWARRAY  
  24.             code.put11(opcode, operand);  
  25.         }  
  26.     }  
  27.   
  28.     public void visitMethodInsn(  
  29.         final int opcode,  
  30.         final String owner,  
  31.         final String name,  
  32.         final String desc)  
  33.     {  
  34.         boolean itf = opcode == Opcodes.INVOKEINTERFACE;  
  35.         Item i = cw.newMethodItem(owner, name, desc, itf);  
  36.         int argSize = i.intVal;  
  37.         // Label currentBlock = this.currentBlock;  
  38.         if (currentBlock != null) {  
  39.             if (compute == FRAMES) {  
  40.                 currentBlock.frame.execute(opcode, 0, cw, i);  
  41.             } else {  
  42.                 /* 
  43.                  * computes the stack size variation. In order not to recompute 
  44.                  * several times this variation for the same Item, we use the 
  45.                  * intVal field of this item to store this variation, once it 
  46.                  * has been computed. More precisely this intVal field stores 
  47.                  * the sizes of the arguments and of the return value 
  48.                  * corresponding to desc. 
  49.                  */  
  50.                 if (argSize == 0) {  
  51.                     // the above sizes have not been computed yet,  
  52.                     // so we compute them...  
  53.                     argSize = getArgumentsAndReturnSizes(desc);  
  54.                     // ... and we save them in order  
  55.                     // not to recompute them in the future  
  56.                     i.intVal = argSize;  
  57.                 }  
  58.                 int size;  
  59.                 if (opcode == Opcodes.INVOKESTATIC) {  
  60.                     size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1;  
  61.                 } else {  
  62.                     size = stackSize - (argSize >> 2) + (argSize & 0x03);  
  63.                 }  
  64.                 // updates current and max stack sizes  
  65.                 if (size > maxStackSize) {  
  66.                     maxStackSize = size;  
  67.                 }  
  68.                 stackSize = size;  
  69.             }  
  70.         }  
  71.         // adds the instruction to the bytecode of the method  
  72.         if (itf) {  
  73.             if (argSize == 0) {  
  74.                 argSize = getArgumentsAndReturnSizes(desc);  
  75.                 i.intVal = argSize;  
  76.             }  
  77.             code.put12(Opcodes.INVOKEINTERFACE, i.index).put11(argSize >> 20);  
  78.         } else {  
  79.             code.put12(opcode, i.index);  
  80.         }  
  81.     }  
  82. }  

通过注释我们可以大概理解visitIntInsn和visitMethodInsn方法的意思。
比如visitIntInsn先计算stack的size,然后根据opcode来判断是SIPUSH指令还是BIPUSH or NEWARRAY指令,并相应的调用字节码修改相关的方法。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多