• class文件结构浅析(2)


    欢迎转载,转载需声明出处

    ------------------

    请先看上一篇:Class类文件结构浅析

    上一篇讲的都是理论。以下我们亲自实践一下。

    首先编写一个简单的java类:
    public class Test
    {
        private int m;
        private String str;
        
        public int func(int m,String str)
        {
            str += "OK";
            m = 10;
            return -1;
        }
        
        public static void main(String[] arg)
        {
            String str = "test";
            int m = 20;
            new Test().func(m,str);
        }
    }
    紧接着我们使用javac编译器将其编译为class字节码格式。这篇文章以分析这个类的字节码为主。分析之前先介绍两个工具,一个是winHex,16进制编辑器,能够以16进制的形式查看class源文件。还有一个是jdk自带的反编译工具javap。我们能够一边分析16进制数据,一边对比着javap编译后的结果。

    首先使用javap -verbose命令查看反编译结果。

    C:UsersRowand jjDesktop>javap -verbose Test
    Classfile /C:/Users/Rowand jj/Desktop/Test.class
      Last modified 2014-5-2; size 616 bytes
      MD5 checksum cfd6b2e7d9d99dda3b84d1dbe87ce657
      Compiled from "Test.java"
    public class Test
      SourceFile: "Test.java"
      minor version: 0
      major version: 51
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #11.#26        //  java/lang/Object."<init>":()V
       #2 = Class              #27            //  java/lang/StringBuilder
       #3 = Methodref          #2.#26         //  java/lang/StringBuilder."<init>":(
    )V
       #4 = Methodref          #2.#28         //  java/lang/StringBuilder.append:(Lj
    ava/lang/String;)Ljava/lang/StringBuilder;
       #5 = String             #29            //  OK
       #6 = Methodref          #2.#30         //  java/lang/StringBuilder.toString:(
    )Ljava/lang/String;
       #7 = String             #31            //  test
       #8 = Class              #32            //  Test
       #9 = Methodref          #8.#26         //  Test."<init>":()V
      #10 = Methodref          #8.#33         //  Test.func:(ILjava/lang/String;)I
      #11 = Class              #34            //  java/lang/Object
      #12 = Utf8               m
      #13 = Utf8               I
      #14 = Utf8               str
      #15 = Utf8               Ljava/lang/String;
      #16 = Utf8               <init>
      #17 = Utf8               ()V
      #18 = Utf8               Code
      #19 = Utf8               LineNumberTable
      #20 = Utf8               func
      #21 = Utf8               (ILjava/lang/String;)I
      #22 = Utf8               main
      #23 = Utf8               ([Ljava/lang/String;)V
      #24 = Utf8               SourceFile
      #25 = Utf8               Test.java
      #26 = NameAndType        #16:#17        //  "<init>":()V
      #27 = Utf8               java/lang/StringBuilder
      #28 = NameAndType        #35:#36        //  append:(Ljava/lang/String;)Ljava/l
    ang/StringBuilder;
      #29 = Utf8               OK
      #30 = NameAndType        #37:#38        //  toString:()Ljava/lang/String;
      #31 = Utf8               test
      #32 = Utf8               Test
      #33 = NameAndType        #20:#21        //  func:(ILjava/lang/String;)I
      #34 = Utf8               java/lang/Object
      #35 = Utf8               append
      #36 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
      #37 = Utf8               toString
      #38 = Utf8               ()Ljava/lang/String;
    {
      public Test();
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 1: 0
    
      public int func(int, java.lang.String);
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=3, args_size=3
             0: new           #2                  // class java/lang/StringBuilder
             3: dup
             4: invokespecial #3                  // Method java/lang/StringBuilder.
    "<init>":()V
             7: aload_2
             8: invokevirtual #4                  // Method java/lang/StringBuilder.
    append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            11: ldc           #5                  // String OK
            13: invokevirtual #4                  // Method java/lang/StringBuilder.
    append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            16: invokevirtual #6                  // Method java/lang/StringBuilder.
    toString:()Ljava/lang/String;
            19: astore_2
            20: bipush        10
            22: istore_1
            23: iconst_m1
            24: ireturn
          LineNumberTable:
            line 8: 0
            line 9: 20
            line 10: 23
    
      public static void main(java.lang.String[]);
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=3, locals=3, args_size=1
             0: ldc           #7                  // String test
             2: astore_1
             3: bipush        20
             5: istore_2
             6: new           #8                  // class Test
             9: dup
            10: invokespecial #9                  // Method "<init>":()V
            13: iload_2
            14: aload_1
            15: invokevirtual #10                 // Method func:(ILjava/lang/String
    ;)I
            18: pop
            19: return
          LineNumberTable:
            line 15: 0
            line 16: 3
            line 17: 6
            line 18: 19
    }
    大家可能看的有点晕,不要紧,我们一步一步来分析。以下我们使用winhex打开Test.class文件:

    准备工作都做好了,以下開始分析吧。假设你对class文件一无所知,请先阅读上一篇文章。
    通过上一篇文章,我们了解了class文件的基本结构,首先来回想一下,class文件是由一组以8位字节为基础单位的二进制流,各个数据项目严格依照顺序紧凑的排列在一起,中间没有不论什么分隔符。

    class文件格式以一种类似c语言结构体的伪结构存储,这样的伪结构仅仅有两种数据类型:

    无符号数和表。无符号数是基本数据类型,以u1 u2 u4 u8代表1字节 2字节 4字节 8字节,表是由多个无符号数或者其它表构成的复合结构。

    整个class文件就是一张表,其结构例如以下:

    ClassFile {
        u4 magic;//魔数(0xCAFEBABE)
        u2 minor_version;//次版本
        u2 major_version;//主版本
        u2 constant_pool_count;//常量池容量计数值
        cp_info constant_pool[constant_pool_count-1];//常量池
        u2 access_flags;//訪问标志
        u2 this_class;//类索引
        u2 super_class;//父类索引
        u2 interfaces_count;//接口计数器
        u2 interfaces[interfaces_count];//接口索引集合
        u2 fields_count;//字段计数器
        field_info fields[fields_count];//字段表
        u2 methods_count;//方法计数器
        method_info methods[methods_count];//方法表
        u2 attributes_count;//属性表计数器
        attribute_info attributes[attributes_count];//属性表集合
    }
    以下看头四个字节,正好是0xCAFEBABE,即所谓的魔数。紧接着四个字节是0x0000 0033。相应10进制为51,这代表当前class文件的版本,查表后发现是JDK1.7.0。

    接下来就是所谓的常量池了,首先是常量池计数器,值为0x0027,相应10进制为39,说明一共同拥有38项常量(第0项保留),对比javap反编译后的汇编代码也能够证实(#38)。

    常量池中的项目类型以一个u1类型的tag标识,比方0x0A(offset为0000 000A),十进制为10,这个tag代表的是CONSTANT_Methodref_info类型(查表可知),表示对类中方法的符号引用。

    紧接着两个字节(u2)为类描写叙述符索引,值为0x000B(第11项常量池数据项),再接着的两个字节是名称和类型描写叙述符,值为0x001A(第26项常量池数据项)。这四个字节都引用了常量池其它数据项。反编译的结果例如以下,能够看到方法的类描写叙述符为java/lang/Object,名称和类型描写叙述符"<init>":<v>.




    第一项常量分析完成,咱们往下看,紧接着一个字节是0x07,该tag相应的表是CONSTANT_Class_info类型:



    所以紧随其后的两个字节为指向全限定名的常量项的索引,值为0x001B,指向常量池第27项常量。例如以下:




    能够发现第27项常量的头一个字节为0x01,即CONSTANT_Utf8_info:



    所以接下来的两个字节为字符串所占的字节数。0x0017。即十进制的23,所以接下来的23个字节即为字符串值。查看winhex可知,值为:java/lang/StringBuilder:



    对比一下汇编代码:


    说明我们解读没有不论什么问题。

    第二项常量分析完成,看第三项,tag值为0x0A,又是一个

    CONSTANT_Methodref_info,依照上面的分析流程分析....
    因为常量池太过庞大,这里仅分析到这。后面的大家能够自己分析,然后对比汇编代码验证你的分析结果。

    常量池之后的表项为access_flags,即訪问标志,占两个字节(u2)。怎样在winhex中找到两个字节呢?这里有个技巧,通过刚才的汇编代码发现常量池最后一项常量为#38,该常量后即为訪问标志位:


    在winhex中找到这个串:

    所以红色圈出来的两个字节即为訪问标志,0x0021。

    这正好是0x0020|0x0001(查表),所以訪问标志为ACC_PUBLIC,ACC_SUPER。查看汇编代码:



    验证了我们的解析结果,是不是非常easy?
    接下来各自是this_class和super_class即本类索引和父类索引,
    this_class值为0x0008,类索引为8。即常量池的第8项:



    super_class值为0x000B,类索引为11,即常量池的第11项:



    能够清晰的看出类名为Test。父类为Object。
    接下来是接口计数器。值为0x0000,代表这个类没有实现不论什么接口。

    至此。我们分析完了类索引。父类索引,接口索引。

    以下到了字段表集合了。

    首先两个字节为字段表计数器。值为0x0002:




    有两个字段。咱仅仅分析第一个字段。

    头两个字节0x0002为訪问标志,相应的是private:


    紧接着两个字节为字段简单名称。0x000C,指向常量池的第12项,值为m。然后0x000D为字段描写叙述符,指向常量池的第13项,值为I。代表Int,最后两个字节为属性表,值为0x0000。到这里我们能够猜測源文件里有个私有成员变量定义:private int m,与事实相符!接下来第二个字段为private String str。

    字段表分析到这,接下来是方法表。事实上方法表跟字段表格式是基本一致的,依照上面流程就可以。首先是方法表计数器0x0003,代表有三个方法(多的那个方法是<init>方法),看第一个,訪问标志位0x0001,相应的是public,然后名称索引位0x0010,指向第16项常量,这是名称索引:
    然后是0x0011,为描写叙述符索引:

    即为init方法。
    好了,方法表就分析到这了。

    整个class文件也分析的差点儿相同了。相信大家看完之后对class文件结构会有进一步的了解的。




  • 相关阅读:
    垂直水平居中几种实现风格
    重绘(repaint)和回流(reflow)
    对象深拷贝
    PhantomJS not found on PATH
    d3.js 数据操作
    canvas 绘制圆弧
    d3.js 柱状图
    d3.js -- 比例尺 scales scaleLinear scaleBand scaleOrdinal scaleTime scaleQuantize
    d3.js -- select、selectAll
    map映射
  • 原文地址:https://www.cnblogs.com/brucemengbm/p/6792690.html
Copyright © 2020-2023  润新知