实践中的Java字节码

对 Java 字节码有了一定了解之后,我们可以来看看一些常用的和熟悉的 Java 语言的内容是如何与字节码映射的,也可以获得一些 Java 实现的细节内容。


 


Java 5:自动封装(autoboxing)


Java 5 版本的一个新特性是自动封装 (autoboxing) ,基础数据类型因语义环境的需要能转换成为对象类型,例如:


 


public class Autoboxing


{


        public static void main(String[] args)


        {


               int x = 5;


               java.util.ArrayList al = new java.util.ArrayList();


               al.add(x);


        }


}


 


在 Java 5 之前,这样的写法是错误的,因为 x 并不是对象。在 Java 5 下,编译后的字节码如下:


 


0: iconst_5


1: istore_1


2: new #2; //class java/util/ArrayList


5: dup


6: invokespecial #3; //Method java/util/ArrayList."<init>":()V


9: astore_2


10: aload_2


11: iload_1


12: invokestatic #4; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;


15: invokevirtual #5; //Method java/util/ArrayList.add:(Ljava/lang/Object;)Z


18: pop


19: return


 


编号为 0 的行将整数常量 5 推送至堆栈,编号为 1 的行将堆栈顶端的 5 存储至第一个本地分片中。接下来,有四个操作符指令, new/dup/invokespecial/astore ,是通常用来新创建对象并存储在本地变量中的做法。接下来,在编号为 10 的行,将 ArrayList 的引用推送至队战,然后再将 x 本地的值推送至堆栈。编号为 12 的行我们看到 Java 调用了静态的 Integer.valueOf 方法,它需要一个单独的堆栈分片,并消费整数值 5 ,然后将包含着 5 的 Integer 对象推送到位。然后,这个对象就成为了 add 方法的参数,调用 add 方法就消费了 Integer 和 ArrayList 的引用,并将 add 方法的返回值推送回堆栈。


 


内部类(Inner Class)


在 JDK 1.1 发布时, Sun 引入了内部类,支持创建与外部类有着特殊的私有可见关系的嵌套类。 JVM 并未引入像 C++ 那样的 friend 功能,这就有点让 Java 使用者有个疑惑:在 JVM 本身强迫私有访问性时,而且把内部类看作跟其他类一样, Java 如何对类的访问进行授权?


 


在下面这个例子中,内部类显然可以访问外部类的 data 私有属性:


 


class Outer


{


        private int data = 12;


        public Inner getInner()


        {


               return new Inner();


        }


        public class Inner


        {


               public int getData()


               {


                       return data;


               }


        }


}


 


public class NestedFun


{


        public static void main(String[] args)


        {


               Outer o = new Outer();


               Outer.Inner i = o.getInner();


               System.out.println(i.getData());


               // prints 12; how?


        }


}


 


对于这段代码,编译器如何进行工作呢?我们从 NestedFun.main(String[]) 开始看字节码:


 


public static void main(java.lang.String[]);


        Code:


               Stack=2, Locals=3, Args_size=1


               0: new #2; //class Outer


               3: dup


               4: invokespecial #3; //Method Outer."<init>":()V


               7: astore_1


               8: aload_1


               9: invokevirtual #4; //Method Outer.getInner:()LOuter$Inner;


               12: astore_2


               13: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream;


               16: aload_2


               17: invokevirtual #6; //Method Outer$Inner.getData:()I


               20: invokevirtual #7; //Method java/io/PrintStream.println:(I)V


               23: return


 


这段字节码还是比较直接的: Java 使用了常用的 new/dup/invokespecial/astore 组合来创建 Outer 的实例,对 Outer.getInner() 和 getData() 的调用,其中对 getData() 调用的返回值直接传入了 println() 方法 ( 注意,编译器选择先获取 System.out ,然后再是 getData() ,所以才能保证执行堆栈的位置顺序正确 ) 。这一段基本没啥,我们再来看 Outer.Inner.getDate() 方法:


 


public class Outer$Inner extends java.lang.Object


        SourceFile: "NestedFun.java"


        InnerClass:


               public #21= #4 of #18; //Inner=class Outer$Inner of class Outer


        minor version: 0


        major version: 50


        Constant pool: (snipped)


 


{


final Outer this$0;


 


public Outer$Inner(Outer);


        Code:


               Stack=2, Locals=2, Args_size=2


               0: aload_0


               1: aload_1


               2: putfield #1; //Field this$0:LOuter;


               5: aload_0


               6: invokespecial #2; //Method java/lang/Object."<init>":()V


               9: return


 


public int getData();


        Code:


               Stack=1, Locals=1, Args_size=1


               0: aload_0


               1: getfield #1; //Field this$0:LOuter;


               4: invokestatic #3; //Method Outer.access$000:(LOuter;)I


               7: ireturn


}


 


这是去掉了一些输出后的结果,以便阅读。首先,我们看到了在 Java 规范中的“ outer this ”引用被显式加入内部类中作为一个属性,名为“ this$0 ”,并标记为 final 。其次,编译器也生成了内部类的构造函数,用一个外部类的引用为“ outer this ”赋值,所以我们可以假定在外部类的 getInner() 方法中的 new Inner() 会用到本构造函数。第三,在内部类的 getData() 方法上,访问了一个外部类的静态方法叫“ access$000 ”,来获取数据。


 


紧接着,我们可以看看外部类。


 


class Outer extends java.lang.Object{


private int data;


 


Outer();


        Code:


               0: aload_0


               1: invokespecial #2; //Method java/lang/Object."<init>":()V


               4: aload_0


               5: bipush 12


                7: putfield #1; //Field data:I


               10: return


 


public Outer$Inner getInner();


        Code:


               0: new #3; //class Outer$Inner


               3: dup


               4: aload_0


               5: invokespecial #4; //Method Outer$Inner."<init>":(LOuter;)V


               8: areturn


 


static int access$000(Outer);


        Code:


               0: aload_0


               1: getfield #1; //Field data:I


               4: ireturn


}


 


我们可以看见编译器生成了一个静态方法专为访问 data 开了个口子,不过“ access$000 ”是包内私有的,也就是说在同包内的类才能访问该方法。


 


Java字节码工具


Java 字节码功能工具很多,包括:


  • Javassist
  • Jasmin
  • ……

 


也许,最重要的拆解字节码的工具还是javap。

The Java Virtual Machine Specification(2nd Edition) JVM规范(第二版)