Java 杂谈程序员我爱编程

记一次解析class文件过程

2018-10-27  本文已影响0人  msrpp

编写一个简单的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;
}

上一篇下一篇

猜你喜欢

热点阅读