Java class文件结构(实例篇)

 在《JAVA CLASS的文件结构(规范篇)》中我们基本完整的描述了class的文件结构规范,这一篇我们以一个实际的例子,来逐行分析class的二进制格式,从而深入了解class文件结构。
 分析代码如下:

package com.sunny.jdk.classfile;

/**
 * <Description> <br>
 *
 * @author Sunny<br>
 * @version 1.0<br>
 * @taskId: <br>
 * @createDate 2018/09/17 15:52 <br>
 * @see com.sunny.jdk.classfile <br>
 */
public class TestClass {
    private int m;

    public int inc() {
        return m + 1;
    }
}

 class文件使用winhex打开的二进制结构如下:



下面我们就根据前篇所说的Class的文件结构来解析以下上图中字节流:

1. 魔数

 从Class的文件结构我们知道,刚开始的4个字节是魔数,上图中从地址000000000-000000003的内容就是魔数,从上图可知Class的文件的魔数是0xCAFEBABE

2. 主副版本号

 接下来的4个字节是主副版本号,有上图可知从000000004-000000005对应的是0x0000,因此Class的minor_version 为0x0000,从000000006-000000007对应的内容为0x0034,因此Class文件的major_version版本为 0x0034,这正好就是jdk1.8编译后的Class对应的主次版本。

3. 常量池数量

 接下来的2个字节从000000008-000000009表示常量池的数量,由上图可以知道其值为0x0016,十进制为22个,但是对于常量池的数量 需要明确一点,常量池的数量是constant_pool_count-1,为什么减一,是因为索引0表示class中的数据项不引用任何常量池中的常 量。所以常量池的个数应该是21个;

4. 常量池

 前篇已经说过了常量池中有不同类型的常量,下面就来看看TestClass.class的第一个常量,每个常量都有一个u1类型的tag标识来表示 常量的类型,上图中00000000A处的内容为0x0A,转换成二级制是10,有上面的关于常量类型的描述可知tag10的常量是Constant_Methodref_info,而Constant_Methodref_info的结构如下:

CONSTANT_Methodref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

 其中tag对应上图位置00000000A处的内容为0x0Aclass_index是接下来两个字节的数据,对应的上图位置的00000000B-00000000C,其值为0x0004,也就是说指向第四个常量;name_and_type_index指向常量池中类型为CONSTANT_NameAndType_info常量。从上图可以看出name_and_type_index的值为0x0012,对应上图的位置是00000000D-00000000E表示指向常量池中的第18个常量。接下来我们可以找到常量池中所有的常量:

  • 第2个常量:
    tag对应00000000F位置的内容是0x09,表示CONSTANT_Fieldref_info,其结构如下:
CONSTANT_Fieldref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

 其中tag对应上图位置00000000F处的内容为0x09;class_index是接下来两个字节的数据,对应的上图位置的000000100-000000101,其值为0x0003,也就是说指向第3个常量;name_and_type_index指向常量池中类型为CONSTANT_NameAndType_info常量。从上图可以看出name_and_type_index的值为0x0013,对应上图的位置是000000102-000000103表示指向常量池中的第19个常量。

  • 第3个常量:
    tag对应000000104位置的内容是0x07,表示CONSTANT_Class_info,其结构如下:
CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}

 其中tag对应上图位置000000104处的内容为0x07name_index是接下来两个字节的数据,对应的上图位置的000000105-000000106,其值为0x0014,也就是说指向第20个常量;

  • 第4个常量:
    tag对应000000107位置的内容是0x07,表示CONSTANT_Class_info,其结构如下:
CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}

 其中tag对应上图位置000000107处的内容为0x07;name_index是接下来两个字节的数据,对应的上图位置的000000108-000000109,其值为0x0015,也就是说指向第21个常量;

  • 第5个常量:
    tag对应00000010A位置的内容是0x01,表示CONSTANT_Utf8_info,其结构如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

 其中tag对应上图位置00000010A处的内容为0x01;length是接下来两个字节的数据,对应的上图位置的00000010B-00000010C,其值为0x0001,也就是length为1;紧接着后面length个字节表示的数据即为该数据,因为length=1,所以00000010D处的数据0x6D表示的即为该数据,转换成ASCII即为m

  • 第6个常量:
    tag对应00000010E位置的内容是0x01,表示CONSTANT_Utf8_info,其结构如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

 其中tag对应上图位置00000010E处的内容为0x01;length是接下来两个字节的数据,对应的上图位置的00000010F-000000200,其值为0x0001,也就是length为1;紧接着后面length个字节表示的数据即为该数据,因为length=1,所以000000201处的数据0x49表示的即为该数据,转换成ASCII即为I

  • 第7个常量:
    tag对应000000202位置的内容是0x01,表示CONSTANT_Utf8_info,其结构如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

 其中tag对应上图位置000000202处的内容为0x01;length是接下来两个字节的数据,对应的上图位置的000000203-000000204,其值为0x0006,也就是length为6;紧接着后面length个字节表示的数据即为该数据,因为length=6,所以000000205-00000020A处的数据0x3C, 0x69,0x6E,0x69, 0x74, 0x3E表示的即为该数据,转换成ASCII即为<init>

  • 第8个常量:
    tag对应00000020B位置的内容是0x01,表示CONSTANT_Utf8_info,其结构如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

 其中tag对应上图位置00000020B处的内容为0x01;length是接下来两个字节的数据,对应的上图位置的00000020C-00000020D,其值为0x0003,也就是length为3;紧接着后面length个字节表示的数据即为该数据,因为length=3,所以00000020E-000000300处的数据0x28, 0x29,0x56表示的即为该数据,转换成ASCII即为()V

  • 第9个常量:
    tag对应000000301位置的内容是0x01,表示CONSTANT_Utf8_info,其结构如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

 其中tag对应上图位置000000301处的内容为0x01;length是接下来两个字节的数据,对应的上图位置的000000302-000000303,其值为0x0004,也就是length为4;紧接着后面length个字节表示的数据即为该数据,因为length=4,所以000000304-000000307处的数据0x43, 0x6F,0x64,0x65表示的即为该数据,转换成ASCII即为Code

  • 第10个常量:
    tag对应000000308位置的内容是0x01,表示CONSTANT_Utf8_info,其结构如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

 其中tag对应上图位置000000308处的内容为0x01;length是接下来两个字节的数据,对应的上图位置的000000309-00000030A,其值为0x000F,也就是length为15;紧接着后面length个字节表示的数据即为该数据,因为length=15,所以00000030B-000000409处的数据0x4C, 0x69,0x6E,0x65,0x4E,0x75,0x6D,0x62,0x65,0x72,0x54,0x61,0x62,0x6C,0x65表示的即为该数据,转换成ASCII即为LineNumberTable

  • 第11个常量:
    tag对应00000040A位置的内容是0x01,表示CONSTANT_Utf8_info,其结构如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

 其中tag对应上图位置00000040A处的内容为0x01;length是接下来两个字节的数据,对应的上图位置的000000409-00000040A,其值为0x0012,也就是length为18;紧接着后面length个字节表示的数据即为该数据,因为length=18,所以00000040B-00000050E处的数据0x4C,0x6F,0x63,0x61,0x6C,0x56,0x61,0x72,0x69,0x61,0x62,0x6C,0x65,0x54,0x61,0x62,0x6C,0x65表示的即为该数据,转换成ASCII即为LocalVariableTable

  • 第12个常量:
    tag对应00000050F位置的内容是0x01,表示CONSTANT_Utf8_info,其结构如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

 其中tag对应上图位置00000050F处的内容为0x01;length是接下来两个字节的数据,对应的上图位置的000000600-000000601,其值为0x0004,也就是length为4;紧接着后面length个字节表示的数据即为该数据,因为length=4,所以000000602-000000605处的数据0x74,0x68,0x69,0x73表示的即为该数据,转换成ASCII即为this

  • 第13个常量:
    tag对应000000606位置的内容是0x01,表示CONSTANT_Utf8_info,其结构如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

 其中tag对应上图位置000000606处的内容为0x01;length是接下来两个字节的数据,对应的上图位置的000000607-000000608,其值为0x0023,也就是length为35;紧接着后面length个字节表示的数据即为该数据,因为length=35,所以000000609-00000080B处的数据如图:


,转换成ASCII即为Lcom/sunny/jdk/classfile/TestClass;

  • 第14个常量:
    tag对应00000080C位置的内容是0x01,表示CONSTANT_Utf8_info,其结构如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

 其中tag对应上图位置00000080C处的内容为0x01;length是接下来两个字节的数据,对应的上图位置的00000080D-00000080E,其值为0x0003,也就是length为3;紧接着后面length个字节表示的数据即为该数据,因为length=3,所以00000080F-000000901处的数据0x69,0x6E,0x63表示的即为该数据,转换成ASCII即为inc

  • 第15个常量:
    tag对应000000902位置的内容是0x01,表示CONSTANT_Utf8_info,其结构如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

 其中tag对应上图位置000000902处的内容为0x01;length是接下来两个字节的数据,对应的上图位置的000000903-000000904,其值为0x0003,也就是length为3;紧接着后面length个字节表示的数据即为该数据,因为length=3,所以000000905-000000907处的数据0x28,0x29,0x49表示的即为该数据,转换成ASCII即为()I

  • 第16个常量:
    tag对应000000908位置的内容是0x01,表示CONSTANT_Utf8_info,其结构如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

 其中tag对应上图位置000000908处的内容为0x01;length是接下来两个字节的数据,对应的上图位置的000000909-00000090A,其值为0x000A,也就是length为10;紧接着后面length个字节表示的数据即为该数据,因为length=10,所以00000090B-000000A04处的数据如图:


,转换成ASCII即为SourceFile

  • 第17个常量:
    tag对应000000A05位置的内容是0x01,表示CONSTANT_Utf8_info,其结构如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

 其中tag对应上图位置000000A05处的内容为0x01;length是接下来两个字节的数据,对应的上图位置的000000A06-000000A07,其值为0x000E,也就是length为14;紧接着后面length个字节表示的数据即为该数据,因为length=14,所以000000A08-000000B05处的数据如图:


,转换成ASCII即为TestClass.java

  • 第18个常量:
    tag对应000000B06位置的内容是0x0C,表示CONSTANT_NameAndType_info,其结构如下:
CONSTANT_NameAndType_info {
    u1 tag;
    u2 name_index; 
    u2 descriptor_index;
}

 其中tag对应上图位置000000B06处的内容为0x0C;name_index是接下来两个字节的数据,对应的上图位置的000000B07-000000B08,其值为0x0007,也就是说指向第7个常量;descriptor_index是接下来的2个字节,从上图可以看出descriptor_index的值为0x0008,对应上图的位置是000000B09-000000B0A表示指向常量池中的第8个常量。

  • 第19个常量:
    tag对应000000B0B位置的内容是0x0C,表示CONSTANT_NameAndType_info,其结构如下:
CONSTANT_NameAndType_info {
    u1 tag;
    u2 name_index; 
    u2 descriptor_index;
}

 其中tag对应上图位置000000B0B处的内容为0x0C;name_index是接下来两个字节的数据,对应的上图位置的000000B0C-000000B0D,其值为0x0005,也就是说指向第5个常量;descriptor_index是接下来的2个字节,从上图可以看出descriptor_index的值为0x0006,对应上图的位置是000000B0E-000000B0F表示指向常量池中的第6个常量。

  • 第20个常量:
    tag对应000000C00位置的内容是0x01,表示CONSTANT_Utf8_info,其结构如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

 其中tag对应上图位置000000C00处的内容为0x01;length是接下来两个字节的数据,对应的上图位置的000000C01-000000C02,其值为0x0021,也就是length为33;紧接着后面length个字节表示的数据即为该数据,因为length=33,所以000000C03-000000E03处的数据如图:


,转换成ASCII即为com/sunny/jdk/classfile/TestClass

  • 第21个常量:
    tag对应000000E04位置的内容是0x01,表示CONSTANT_Utf8_info,其结构如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

 其中tag对应上图位置000000E04处的内容为0x01;length是接下来两个字节的数据,对应的上图位置的000000E05-000000E06,其值为0x0010,也就是length为16;紧接着后面length个字节表示的数据即为该数据,因为length=16,所以000000E07-000000F06处的数据如图:


,转换成ASCII即为java/lang/Object

 这样常量池的21个常量全部分析完毕,不过JDK提供了一个方便的工具可以让我们查看常量池中所包含的常量。通过javap -verbose TestClass 即可得到所有常量池中的常量,如下所示:

C:\ProgramFiles\Java\jdk1.8.0_144\bin>javap -verbose E:\workspace\workspace_java_tool\workspace_sunny_project\java-honey-collection\target\classes\com\sunny\jdk\classfile\TestClass.class
Classfile /E:/workspace/workspace_java_tool/workspace_sunny_project/java-honey-collection/target/classes/com/sunny/jdk/classfile/TestClass.class
  Last modified 2018-10-23; size 401 bytes
  MD5 checksum b02768ba5213ac8bcfdcd0865e8d7374
  Compiled from "TestClass.java"
public class com.sunny.jdk.classfile.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         // com/sunny/jdk/classfile/TestClass.m:I
   #3 = Class              #20            // com/sunny/jdk/classfile/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               Lcom/sunny/jdk/classfile/TestClass;
  #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               com/sunny/jdk/classfile/TestClass
  #21 = Utf8               java/lang/Object
{
  public com.sunny.jdk.classfile.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 12: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/sunny/jdk/classfile/TestClass;

  public int inc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field m:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 16: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  this   Lcom/sunny/jdk/classfile/TestClass;
}
SourceFile: "TestClass.java"

 由此常量池已经全部分析完毕,接下来我们即将分access_flags。

5. u2 access_flags

 access_flags表示类或者接口方面的访问信息,比如Class表示的是类还是接口,是否为public,static,final等。具体访问标示的含义之前已经说过 了,下面我们就来看看TestClass的访问标示。Class的访问标示是000000F07-000000F08,期值为0x0021,根据前面说的 各种访问标示的标志位,我们可以知道:0x0021=0x0001|0x0020 也即ACC_PUBLIC 和 ACC_SUPER为真,其中ACC_PUBLIC大家好理解,ACC_SUPER是jdk1.0.2之后编译的类都会带有的标志。

6. u2 this_class 和 u2 super_class

 this_class表示当前类的索引值,super_class 表示当前类的父类的索引值;索引值所指向的常量池中类型为CONSTANT_Class_info的常量;例子中类索引值的是000000F09-000000F0A所表示的值0x0003,也即常量池的第3个常量,从而可知,this_class的全限定名为com/sunny/jdk/classfile/TestClass;同理,super_class是000000F0B-000000F0C所表示的值0x0004,也即常量池的第4个常量,从而可知,super_class的全限定名为java/lang/Object

6. u2 interfaces_count和 interfaces[interfaces_count]

 interfaces_count和 interfaces[interfaces_count]表示接口数量以及具体的每一个接口;TestClass的接口数量是000000F0D-000000F0E所表示的值0x0000,也即没有实现任何接口;

7. u2 fields_count 和 field_info

  fields_count表示类中field_info表的数量,而field_info表示类的实例变量和类变量,这里需要注意的是 field_info不包含从父类继承过来的字段,field_info的结构如下:

field_info {
    u2 access_flags;
    u2 name_index;
    u2 descriptor_index;
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

 例子中接口后面紧接着两个字节000000F0F-000001000中的0x0001即为字段的个数,从而可知字段的个数为1,与代码样例相符;后面即为字段的详细信息;
 access_flags 是000001001-000001002中0x0002,对照上一篇的字段修饰符表格可知字段的修饰符为ACC_PRIVATE(0x0002);name_index是000001003-000001004表达的0x0005,表示常量池中的第5个常量,也即是m,表示字段名为m;descriptor_index是000001005-000001006表达的0x0006,表示常量池中的第6个常量,也I,表示int类型;attributes_count是000001007-000001008位置的0x0000,表示没有属性;没有属性的话,后面就是methods_count和method_info;

8. u2 methods_count 和 method_info

 其中methods_count表示方法的数量,而method_info表示的方法表,其中方法表的结构如下所示:

method_info {
    u2 access_flags;
    u2 name_index;
    u2 descriptor_index;
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

 methods_count是000001009-00000100A位置的0x0002,表示有2个method;

  • 第1个方法:
    access_flags是00000100B-00000100C位置的0x0001,表示ACC_PUBLIC(0x0001);name_index是00000100D-00000100E位置的0x0007,表示常量池的第7个常量,也即方法名为<init>;descriptor_index是00000100F-000001100位置的0x0008,也即常量池的第8个常量,也即()V;attributes_count是000001101-000001102位置的0x0001,表示有1个属性,后面紧跟的就是属性,属性通用结构如下,不同的属性结构也不完全一样:
attribute_info {
    u2 attribute_name_index;   //属性名索引
    u4 attribute_length;       //属性长度
    u1 info[attribute_length]; //属性的具体内容
}
  • <init>方法的第1个属性:
    attribute_name_index是000001103-000001104位置的0x0009,表示常量池的第9个常量,也即Code,表示该属性是Code属性,Code的属性结构如下:
Code_attribute {
    u2 attribute_name_index; 
    u4 attribute_length;
    u2 max_stack;   
    u2 max_locals;  
    u4 code_length; 
    u1 code[code_length]; 
    u2 exception_table_length;
    {   u2 start_pc;
        u2 end_pc;
        u2 handler_pc; 
        u2 catch_type; 
    } exception_table[exception_table_length]; 
    u2 attributes_count;     
    attribute_info attributes[attributes_count]; 
}

 attribute_length(attribute_length不包括attribute_name_index和attribute_length的6个字节的长度)为000001105-000001108位置的0x0000002F,表示属性长度为47;max_stack(max_stack表示最大栈深度,虚拟机在运行时根据这个值来分配栈帧中操作数的深度)为000001109-00000110A位置的0x0001,表示最大栈帧深度为1;max_locals(max_locals代表了局部变量表的存储空间)为00000110B-00000110C位置的0x0001,表示局部变量的存储空间为1;code_length(code_length代表了字节码指令的数量,而code表示的是字节码指令,从Code属性结构中可以知道code的类型为u1,一个u1类型的取值为0x00-0xFF,对应的十进制为0-255,目前虚拟机规范已经定义了200多条指令)为00000110D-000001200位置的0x00000005,表示Code字节码指令的数量为5;后面的5个字节即为字节码,位置000001201-000001205的数据为“2A B7 00 01 B1”,翻译如下:

  • 读入2A,查虚拟机规范1.8知道指令操作码0x2A对应的指令为aload_0,这个指令的含义是讲第0个Slot中为reference类型的本地变量推送到操作数栈顶;
  • 读入B7,查表得0xB7对应的指令为invokespecial,这条指令的作用是以栈顶的reference类型的数据所指向的对象作为方法接受者,调用此对象的实例构造器方法、private方法或者它的父类的方法。这个方法有一个u2类型的参数说明具体调用哪一个方法,它指向常量池中的一个CONSTANT_Methodref_info类型常量,即此方法的符号引用。
  • 读入00 01,这是invokespecial的参数,查常量池得0x0001对应的常量为实例构造器<init>方法的符号引用;
  • 读入B1,查表得0xB1对应的指令为return,含义是返回此方法,并且返回值为void,这条指令执行后,当前方法结束;

 exception_table_length为000001206-000001207位置的0x0000,表示exception_table_length为0,后面没有exception相关数据;紧接着两个字节表示attributes_count,即位置000001208-0000012090x0002,表示有2个attribute;分析Code属性结构中的两个属性如下:

  • 第1个属性:
     attribute_name_index为00000120A-00000120B位置的0x000A,表示引用常量池的第10个常量,也即LineNumberTable,LineNumberTable的属性结构如下:
LineNumberTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 line_number_table_length;
    {   u2 start_pc;
        u2 line_number;
      } line_number_table[line_number_table_length];
}

 attribute_length为00000120C-00000120F位置的0x00000006,表示属性长度为6;line_number_table_length为000001300-000001301位置的0x0001,也即拥有一个line_number_table;后面紧跟着line_number_table,其中start_pc为000001302-000001303位置的0x0000,line_number为000001304-000001305位置0x000C;

  • 第2个属性:
     attribute_name_index为000001306-000001307位置的0x000B,表示引用常量池的第11个常量,也即LocalVariableTable,LocalVariableTable的属性结构如下:
LocalVariableTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 local_variable_table_length
    { u2 start_pc;
      u2 length;
      u2 name_index;
      u2 descriptor_index;
      u2 index;
    } local_variable_table[local_variable_table_length];
}

 attribute_length为000001308-00000130B位置的0x0000000C,表示属性长度为12;local_variable_table_length为00000130C-00000130D位置的0x0001,表示local_variable_table的长度为1;紧接着local_variable_table的分析,其中start_pc为00000130E-00000130F位置的0x0000,length为000001400-000001401位置的0x0005,name_index为000001402-000001403位置的0x000C,表示name为常量池的第12个属性,也即this,descriptor_index为000001404-000001405位置的0x000D,也即常量池的第13个属性,也即Lcom/sunny/jdk/classfile/TestClass;,index为000001406-000001407位置的0x0000

  • 第2个方法:

 access_flags为000001408-000001409位置的0x0001,表示ACC_PUBLIC(0x0001);name_index是00000140A-00000140B位置的0x000E,表示常量池的第14个常量,也即方法名为inc;descriptor_index是00000140C-00000140D位置的0x000F,也即常量池的第15个常量,也即()I,表示返回值为int;attributes_count是00000140E-00000140F位置的0x0001,表示有inc方法有1个属性,后面紧跟的就是属性分析; *inc方法的第1个属性: attribute_name_index是000001500-000001501位置的0x0009,表示常量池的第9个常量,也即Code`,表示该属性是Code属性,Code的属性结构如下:

Code_attribute {
    u2 attribute_name_index; 
    u4 attribute_length;
    u2 max_stack;   
    u2 max_locals;  
    u4 code_length; 
    u1 code[code_length]; 
    u2 exception_table_length;
    {   u2 start_pc;
        u2 end_pc;
        u2 handler_pc; 
        u2 catch_type; 
    } exception_table[exception_table_length]; 
    u2 attributes_count;     
    attribute_info attributes[attributes_count]; 
}

 attribute_length为000001502-000001505位置的0x00000031,表示属性长度为49;max_stack(max_stack表示最大栈深度,虚拟机在运行时根据这个值来分配栈帧中操作数的深度)为000001506-000001507位置的0x0002,表示最大栈帧深度为2;max_locals(max_locals代表了局部变量表的存储空间)为000001508-000001509位置的0x0001,表示局部变量的存储空间为1;code_length(code_length代表了字节码指令的数量,而code表示的是字节码指令,从Code属性结构中可以知道code的类型为u1,一个u1类型的取值为0x00-0xFF,对应的十进制为0-255,目前虚拟机规范已经定义了200多条指令)为00000150A-00000150D位置的0x00000007,表示Code字节码指令的数量为7;后面的7个字节即为字节码,位置00000150E-000001604的数据为“2A B4 00 02 04 60 AC”,翻译如下:

  • 读入2A,查虚拟机规范1.8知道指令操作码0x2A对应的指令为aload_0,这个指令的含义是讲第0个Slot中为reference类型的本地变量推送到操作数栈顶;

  • 读入B4,查表得0xB4对应的指令为getfield,这条指令的作用是获取指定类的实例字段,并将其压入栈顶;这个方法有一个u2类型的参数说明具体调用哪一个类的什么属性,它指向常量池中的一个CONSTANT_Fieldref_info类型常量,即此属性的符号引用。

  • 读入00 02,这是getfield的参数,查常量池得0x0002对应的常量为com/sunny/jdk/classfile/TestClass.m:I

  • 读入04,查表得0x04对应的指令为iconst_1,含义是将int类型2推送至栈顶;

  • 读入60,查表得0x60对应的指令为iadd,含义是将栈顶两int类型的元素相加,并将相加的结果压入栈顶,得到的是1;

  • 读入AC,查表得0xAC对应的指令为ireturn,含义是将当前方法的结果1,返回;

 exception_table_length为000001605-000001606位置的0x0000,表示exception_table_length为0,后面没有exception相关数据;紧接着两个字节表示attributes_count,即位置000001607-0000016080x0002,表示有2个attribute;分析Code属性结构中的两个属性如下:

  • 第1个属性:
     attribute_name_index为000001609-00000160A位置的0x000A,表示引用常量池的第10个常量,也即LineNumberTable,LineNumberTable的属性结构如下:
LineNumberTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 line_number_table_length;
    {   u2 start_pc;
        u2 line_number;
      } line_number_table[line_number_table_length];
}

 attribute_length为00000160B-00000160E位置的0x00000006,表示属性长度为6;line_number_table_length为00000160F-000001700位置的0x0001,也即拥有一个line_number_table;后面紧跟着line_number_table,其中start_pc为000001701-000001702位置的0x0000,line_number为000001703-000001704位置0x0010;

  • 第2个属性:
     attribute_name_index为000001705-000001706位置的0x000B,表示引用常量池的第11个常量,也即LocalVariableTable,LocalVariableTable的属性结构如下:
LocalVariableTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 local_variable_table_length
    { u2 start_pc;
      u2 length;
      u2 name_index;
      u2 descriptor_index;
      u2 index;
    } local_variable_table[local_variable_table_length];
}

 attribute_length为000001707-00000170A位置的0x0000000C,表示属性长度为12;local_variable_table_length为00000170B-00000170C位置的0x0001,表示local_variable_table的长度为1;紧接着local_variable_table的分析,其中start_pc为00000170D-00000170E位置的0x0000,length为00000170F-000001800位置的0x0007,name_index为000001801-000001802位置的0x000C,表示name为常量池的第12个属性,也即this,descriptor_index为000001803-000001804位置的0x000D,也即常量池的第13个属性,也即Lcom/sunny/jdk/classfile/TestClass;,index为000001805-000001806位置的0x0000;最后我们来分析下class的文件属性;

8. class文件属性:u2 attributes_count 和 attributes[attributes_count]

 attributes_count 为000001807-000001808位置的0x0001,表示有一个属性;attribute_name_index为000001809-00000180A位置的0x0010,表示常量池中的第16个属性,也SourceFile属性;SourceFile属性结构如下:

SourceFile_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 sourcefile_index;
}

 attribute_length为位置的00000180B-00000180E位置的0x00000002,表示属性长度为2;sourcefile_index为00000180F-000001900位置的0x0011,表示常量池的第17个常量,也即TestClass.java
 至此,整个class的二进制码全部分析完毕;但是JDK也给我们提供了更方便的分析工具,也即文中提到的javap -verbose class文件命令。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269