最MC论坛

标题: Java Bytecode [打印本页]

作者: Jianghao7172    时间: 2017-8-18 00:13
标题: Java 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:
  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:
  1. 漱壕 1 +

  2. ()V Code LineNumberTable main ([Ljava/lang/String;)V
  3. SourceFile Test.java
  4. ! " 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 !

  5. * > ' < Y
复制代码
使用javap -c Test > Test.bytecode生成的Test.bytecode:
  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."":()V
  7. 4: return

  8. public static void main(java.lang.String[]);
  9. Code:
  10. 0: sipush 10000
  11. 3: istore_1
  12. 4: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
  13. 7: new #3; //class java/lang/StringBuilder
  14. 10: dup
  15. 11: invokespecial #4; //Method java/lang/StringBuilder."":()V
  16. 14: ldc #5; //String Hello Bytecode! Number =
  17. 16: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  18. 19: iload_1
  19. 20: invokevirtual #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  20. 23: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  21. 26: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
  22. 29: return

  23. }
复制代码
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是如何修改字节码的:
  1. class MethodWriter implements MethodVisitor {

  2. private ByteVector code = new ByteVector();

  3. public void visitIntInsn(final int opcode, final int operand) {
  4. // Label currentBlock = this.currentBlock;
  5. if (currentBlock != null) {
  6. if (compute == FRAMES) {
  7. currentBlock.frame.execute(opcode, operand, null, null);
  8. } else if (opcode != Opcodes.NEWARRAY) {
  9. // updates current and max stack sizes only for NEWARRAY
  10. // (stack size variation = 0 for BIPUSH or SIPUSH)
  11. int size = stackSize + 1;
  12. if (size > maxStackSize) {
  13. maxStackSize = size;
  14. }
  15. stackSize = size;
  16. }
  17. }
  18. // adds the instruction to the bytecode of the method
  19. if (opcode == Opcodes.SIPUSH) {
  20. code.put12(opcode, operand);
  21. } else { // BIPUSH or NEWARRAY
  22. code.put11(opcode, operand);
  23. }
  24. }

  25. public void visitMethodInsn(
  26. final int opcode,
  27. final String owner,
  28. final String name,
  29. final String desc)
  30. {
  31. boolean itf = opcode == Opcodes.INVOKEINTERFACE;
  32. Item i = cw.newMethodItem(owner, name, desc, itf);
  33. int argSize = i.intVal;
  34. // Label currentBlock = this.currentBlock;
  35. if (currentBlock != null) {
  36. if (compute == FRAMES) {
  37. currentBlock.frame.execute(opcode, 0, cw, i);
  38. } else {
  39. /*
  40. * computes the stack size variation. In order not to recompute
  41. * several times this variation for the same Item, we use the
  42. * intVal field of this item to store this variation, once it
  43. * has been computed. More precisely this intVal field stores
  44. * the sizes of the arguments and of the return value
  45. * corresponding to desc.
  46. */
  47. if (argSize == 0) {
  48. // the above sizes have not been computed yet,
  49. // so we compute them...
  50. argSize = getArgumentsAndReturnSizes(desc);
  51. // ... and we save them in order
  52. // not to recompute them in the future
  53. i.intVal = argSize;
  54. }
  55. int size;
  56. if (opcode == Opcodes.INVOKESTATIC) {
  57. size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1;
  58. } else {
  59. size = stackSize - (argSize >> 2) + (argSize & 0x03);
  60. }
  61. // updates current and max stack sizes
  62. if (size > maxStackSize) {
  63. maxStackSize = size;
  64. }
  65. stackSize = size;
  66. }
  67. }
  68. // adds the instruction to the bytecode of the method
  69. if (itf) {
  70. if (argSize == 0) {
  71. argSize = getArgumentsAndReturnSizes(desc);
  72. i.intVal = argSize;
  73. }
  74. code.put12(Opcodes.INVOKEINTERFACE, i.index).put11(argSize >> 2, 0);
  75. } else {
  76. code.put12(opcode, i.index);
  77. }
  78. }
  79. }
复制代码
通过注释我们可以大概理解visitIntInsn和visitMethodInsn方法的意思。
比如visitIntInsn先计算stack的size,然后根据opcode来判断是SIPUSH指令还是BIPUSH or NEWARRAY指令,并相应的调用字节码修改相关的方法。

本文转载自 http://hi.baidu.com/yxl1982/blog ... 033bfcd62afc60.html





欢迎光临 最MC论坛 (http://www.zuimc.com/) Powered by Discuz! X3.2