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: - public class Test {
- public static void main(String[] args) {
- int i = 10000;
- System.out.println("Hello Bytecode! Number = " + i);
- }
- }
复制代码编译后的Test.class: - 漱壕 1 +
- ()V Code LineNumberTable main ([Ljava/lang/String;)V
- SourceFile Test.java
- ! " 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 !
- * > ' < Y
复制代码使用javap -c Test > Test.bytecode生成的Test.bytecode: - Compiled from "Test.java"
- public class Test extends java.lang.Object{
- public Test();
- Code:
- 0: aload_0
- 1: invokespecial #1; //Method java/lang/Object."":()V
- 4: return
- public static void main(java.lang.String[]);
- Code:
- 0: sipush 10000
- 3: istore_1
- 4: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
- 7: new #3; //class java/lang/StringBuilder
- 10: dup
- 11: invokespecial #4; //Method java/lang/StringBuilder."":()V
- 14: ldc #5; //String Hello Bytecode! Number =
- 16: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
- 19: iload_1
- 20: invokevirtual #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
- 23: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
- 26: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 29: return
- }
复制代码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是如何修改字节码的: - class MethodWriter implements MethodVisitor {
- private ByteVector code = new ByteVector();
- public void visitIntInsn(final int opcode, final int operand) {
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(opcode, operand, null, null);
- } else if (opcode != Opcodes.NEWARRAY) {
- // updates current and max stack sizes only for NEWARRAY
- // (stack size variation = 0 for BIPUSH or SIPUSH)
- int size = stackSize + 1;
- if (size > maxStackSize) {
- maxStackSize = size;
- }
- stackSize = size;
- }
- }
- // adds the instruction to the bytecode of the method
- if (opcode == Opcodes.SIPUSH) {
- code.put12(opcode, operand);
- } else { // BIPUSH or NEWARRAY
- code.put11(opcode, operand);
- }
- }
- public void visitMethodInsn(
- final int opcode,
- final String owner,
- final String name,
- final String desc)
- {
- boolean itf = opcode == Opcodes.INVOKEINTERFACE;
- Item i = cw.newMethodItem(owner, name, desc, itf);
- int argSize = i.intVal;
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(opcode, 0, cw, i);
- } else {
- /*
- * computes the stack size variation. In order not to recompute
- * several times this variation for the same Item, we use the
- * intVal field of this item to store this variation, once it
- * has been computed. More precisely this intVal field stores
- * the sizes of the arguments and of the return value
- * corresponding to desc.
- */
- if (argSize == 0) {
- // the above sizes have not been computed yet,
- // so we compute them...
- argSize = getArgumentsAndReturnSizes(desc);
- // ... and we save them in order
- // not to recompute them in the future
- i.intVal = argSize;
- }
- int size;
- if (opcode == Opcodes.INVOKESTATIC) {
- size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1;
- } else {
- size = stackSize - (argSize >> 2) + (argSize & 0x03);
- }
- // updates current and max stack sizes
- if (size > maxStackSize) {
- maxStackSize = size;
- }
- stackSize = size;
- }
- }
- // adds the instruction to the bytecode of the method
- if (itf) {
- if (argSize == 0) {
- argSize = getArgumentsAndReturnSizes(desc);
- i.intVal = argSize;
- }
- code.put12(Opcodes.INVOKEINTERFACE, i.index).put11(argSize >> 2, 0);
- } else {
- code.put12(opcode, i.index);
- }
- }
- }
复制代码通过注释我们可以大概理解visitIntInsn和visitMethodInsn方法的意思。 比如visitIntInsn先计算stack的size,然后根据opcode来判断是SIPUSH指令还是BIPUSH or NEWARRAY指令,并相应的调用字节码修改相关的方法。
|