美文网首页Java 杂谈程序员我爱编程
记一次解析class文件过程

记一次解析class文件过程

作者: msrpp | 来源:发表于2018-10-27 18:57 被阅读0次

    编写一个简单的Class文件

    public class TestClass {
        private int m;
        public int inc(){
            return m++;
        }
    }
    

    查看编译出来的Class文件二进制:

    localhost:baseLearning jjchen$ xxd TestClass.class 
    00000000: cafe babe 0000 0034 0016 0a00 0400 1209  .......4........
    00000010: 0003 0013 0700 1407 0015 0100 016d 0100  .............m..
    00000020: 0149 0100 063c 696e 6974 3e01 0003 2829  .I...<init>...()
    00000030: 5601 0004 436f 6465 0100 0f4c 696e 654e  V...Code...LineN
    00000040: 756d 6265 7254 6162 6c65 0100 124c 6f63  umberTable...Loc
    00000050: 616c 5661 7269 6162 6c65 5461 626c 6501  alVariableTable.
    00000060: 0004 7468 6973 0100 0b4c 5465 7374 436c  ..this...LTestCl
    00000070: 6173 733b 0100 0369 6e63 0100 0328 2949  ass;...inc...()I
    00000080: 0100 0a53 6f75 7263 6546 696c 6501 000e  ...SourceFile...
    00000090: 5465 7374 436c 6173 732e 6a61 7661 0c00  TestClass.java..
    000000a0: 0700 080c 0005 0006 0100 0954 6573 7443  ...........TestC
    000000b0: 6c61 7373 0100 106a 6176 612f 6c61 6e67  lass...java/lang
    000000c0: 2f4f 626a 6563 7400 2100 0300 0400 0000  /Object.!.......
    000000d0: 0100 0200 0500 0600 0000 0200 0100 0700  ................
    000000e0: 0800 0100 0900 0000 2f00 0100 0100 0000  ......../.......
    000000f0: 052a b700 01b1 0000 0002 000a 0000 0006  .*..............
    00000100: 0001 0000 0001 000b 0000 000c 0001 0000  ................
    00000110: 0005 000c 000d 0000 0001 000e 000f 0001  ................
    00000120: 0009 0000 0036 0004 0001 0000 000c 2a59  .....6........*Y
    00000130: b400 025a 0460 b500 02ac 0000 0002 000a  ...Z.`..........
    00000140: 0000 0006 0001 0000 0004 000b 0000 000c  ................
    00000150: 0001 0000 000c 000c 000d 0000 0001 0010  ................
    00000160: 0000 0002 0011 
    
    

    Class头

    前面4个字节是固定的cafe babe,标识这是一个class文件,接下来的6个字节和java版本相关0000 0034,版本52 对应jdk1.8中的一个版本。

    常量区

    接下来的0016,代表常量池中有21个常量(从1开始,0保留).常量类型一共有12种,他们的结构都是不一样的,在class文件中紧致排列,十二种类型结构如图(摘自<<深入理解java虚拟机>>):

    8.jpg

    下面根据上图解析class文件常量区:

    1.指向方法的引用(二进制为5个字节:0a 0004 0012),内容有两个(1)指向类描述符CONSTANT_Class_info的索引项(第四项),(2)指向名称及类型描述符CONSTANT_NameAndType的索引项(第18项)。

    2.CONSTANT_Fieldref_info 指向类成员变量的引用(二进制为5个字节:09 0003 0013)内容有两个(1)指向所属的类或者接口的对象(2)指向

    3.CONSTANT_Class_info (二进制为3个字节:07 0014)对应20号常量区TestClass

    4.CONSTANT_Class_info(二进制为3个字节:07 0015)对应21号常量区java/lang/Object

    5.UTF-8常量(二进制为变长结构: 1字节+标识长度的2字节+length字节: 01 0001 6d),m

    6.UTF-8常量(二进制:01 0001 49), I

    7.UTF-8常量(二进制:01 0006 3c69 6e69 743e), <init>

    8.UTF-8常量(二进制:01 0003 2829 56), ()V

    9.UTF-8常量(二进制:01 0004 436f 6465), Code

    10.UTF-8常量(二进制:01 000f 4c69 6e65 4e75 6d62 6572 5461 626c 65),LineNumberTable

    11.UTF-8常量(二进制:01 0012 4c6f 6361 6c56 6172 6961 626c 6554 6162 6c65),LocalVariableTable

    12.UTF-8常量(二进制:0004 7468 6973),this

    13.UTF-8常量(二进制:01 000b 4c54 6573 7443 6c61 7373 3b),LTestClass

    14.UTF-8常量(二进制:01 0003 696e 63),inc

    15.UTF-8常量(二进制:01 0003 2829 49),()I

    16.UTF-8常量(二进制:01 000a 536f 7572 6365 4669 6c65),SourceFile

    17.UTF-8常量(二进制:01 000e 5465 7374 436c 6173 732e 6a61 7661),TestClass.java

    18.CONSTANT_NameAndType 类型(二进制:0c 0007 0008), 内容有两个,(1)指向字段或方法名常量项的索引,0007:<init> ,(2)指向字段或方法名的描述符常量项索引0008:()V

    19.CONSTANT_NameAndType 类型(二进制:0c 0005 0006), (1)m (2)I

    ...

    类索引,父类索引和接口索引

    在常量区之后,是占用两个字节的类标识,我们的类是jdk1.8生成的,所以ACC_SUPER标识是1,结合起来就是0x0021。接下来4个字节代表当前class文件的类索引和父类索引,分别为对应常量区的0003(TestClass)和0004(java/lang/Object),其中如果该类是顶级的Object类,那么父类索引是0。接下来两个字节是当前class实现的接口数量,在后面是接口索引.这里我们没有实现某个接口所以是0x0000.

    4.jpg

    字段表

    再接下来的 0x0001代表类内成员变量数是1,在接下来的8个字节0002 0005 0006 0000 0002代表这个变量是声明为private的,0005是常量区的位置,代表这个变量名是m.如果我们代码声明中还有private int m=123之类的,那么接下来还有一部分二进制数据是用来代表这些额外信息的。我们这里是0000说明数量是0。无需额外空间存储信息。

    方法表

    接下来的00 02代表类内方法的数量,一个是编译器自动添加的<init>,另一个是用户定义的inc,先看第一个0001 0007 0008 0001 0009前两个字节代表方法的访问标识,这里具体是public,中间的4个字节代表方法名及其返回类型:init,返回值()V,合起来即public void init(),最后两个字节代表init方法的属性表有1个值:再接下来4个字节是其值是0009对应常量区Code,Code的结构为:

    3.jpg

    所以接下来是4个字节的长度00 00 002f,(不包括前面的6个字节),所以值为47,max_stack=1,max_locals=1,code_length=5,读入5个字节2a b700 01b1,Code的取值可以从这里查到,查表得2a->aload_0,b7->invokespecial,invokespecial后面跟着2个字节的常量区索引,对应取值0001->java/lang/Object."<init>":()V,b1->return,接下来的exception_info长度为0000,说明这个方法不抛出异常。
    再接下里两个字节0002代表属性的数量是2。读入第一个000a查询常量区,为LineNumberTable,查看此结构:

    5.jpg

    ,length->0000 0006,line_number_table_length->0001,line_number_info有两个2字节的数据,分别代表字节码中的行号和java代码中的行号。这里是00000001
    接下来的属性表是000b,对应常量区LocalVariableTable,看一下LocalVariableTable的结构:

    6.jpg
    7.jpg

    可得:attribute_length->0000 000c,local_variable_table_length->0001,local_variable_info占10个字节,5个值分别为0->字节码起始位置,5->,12->常量区this,13->常量区LTestClass。我们统计一下这个方法的字节码长度,从属性表的max_stack开始计算,正好是47个字节,和Code属性表的attribute_length指示的值是一致的。

    用同样的方法计算出inc方法区,这里忽略详细步骤。

    最后的校验

    最后用javap -verbose来解析一下TestClass.class,可以看到上述流程解析出来的结果是一致的。

    javap -verbose TestClass.class 
    
    Classfile /Users/jjchen/javapro/baseLearning/out/production/baseLearning/TestClass.class
      Last modified 2018-10-26; size 358 bytes
      MD5 checksum 0494d0eec1cf75ded4b39208b436cd6f
      Compiled from "TestClass.java"
    public class TestClass
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #4.#18         // java/lang/Object."<init>":()V
       #2 = Fieldref           #3.#19         // TestClass.m:I
       #3 = Class              #20            // TestClass
       #4 = Class              #21            // java/lang/Object
       #5 = Utf8               m
       #6 = Utf8               I
       #7 = Utf8               <init>
       #8 = Utf8               ()V
       #9 = Utf8               Code
      #10 = Utf8               LineNumberTable
      #11 = Utf8               LocalVariableTable
      #12 = Utf8               this
      #13 = Utf8               LTestClass;
      #14 = Utf8               inc
      #15 = Utf8               ()I
      #16 = Utf8               SourceFile
      #17 = Utf8               TestClass.java
      #18 = NameAndType        #7:#8          // "<init>":()V
      #19 = NameAndType        #5:#6          // m:I
      #20 = Utf8               TestClass
      #21 = Utf8               java/lang/Object
    {
      public TestClass();
        descriptor: ()V
        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
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   LTestClass;
    
      public int inc();
        descriptor: ()I
        flags: ACC_PUBLIC
        Code:
          stack=4, locals=1, args_size=1
             0: aload_0
             1: dup
             2: getfield      #2                  // Field m:I
             5: dup_x1
             6: iconst_1
             7: iadd
             8: putfield      #2                  // Field m:I
            11: ireturn
          LineNumberTable:
            line 4: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      12     0  this   LTestClass;
    }
    
    

    相关文章

      网友评论

        本文标题:记一次解析class文件过程

        本文链接:https://www.haomeiwen.com/subject/bxtctqtx.html