手把手带你读class文件字节码

这是 JVM 系列的第五篇,见 JVM 系列

示例代码基于 jdk1.8 版本

准备

IDEA 里面如果想看 class 的二进制文件需要下载插件 BinEd ,也有 C 端的。另外查看编译后的 class 文件人类友好插件可以选用
jclasslib Bytecode Viewer ,或者用命令 javap -verbose className 来打印出 class 的文件格式。

class 文件格式

前面的文章(JVM 系列 —— class 文件格式 )讲过一个正确的 class 文件格式长什么样子,另外,再强调一点就是,这个 class 文件指的是 class 文件流,并不是单纯指磁盘上的 class 文件,也可能来自网络字节流。 ClassFile 格式:

ClassFile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ClassFile {
u4 magic;
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];
}

最简单的一个类

  • Demo 代码

    我们就用最简单的一个类,再加上一个 int 类型的字段作为示例代码,我放在工程里面的,所以会带有包名,如下:

    demo code
    1
    2
    3
    4
    5
    package top.caolizhi.example.jvm.bytecode;

    public class Bytecode {
    int s = 3;
    }
  • class 文件二进制字节码

    装完插件后,选中类文件,然后点击菜单 View -> Show Bytecode With Jclasslib
    jclasslib
    右变就会出现友好可读的 class 文件的描述:
    class 文件
    我们也可以用命令行来操作,先编译一遍,然后进入到目录 target > classes,在控制台运行命令:javap -verbose top.caolizhi.example.jvm.bytecode.Bytecode,打印如下:

    javap -verbose 命令输出
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    E:\Workspaces\Git\caolizhi-personal\jvm\target\classes>javap -verbose top.caolizhi.example.jvm.bytecode.Bytecode
    Classfile /E:/Workspaces/Git/caolizhi-personal/jvm/target/classes/top/caolizhi/example/jvm/bytecode/Bytecode.class
    Last modified 2021-12-9; size 352 bytes
    MD5 checksum e9c0623eb64cce65bad7f5f5b3e7678c
    Compiled from "Bytecode.java"
    public class top.caolizhi.example.jvm.bytecode.Bytecode
    minor version: 0
    major version: 52
    flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
    #1 = Methodref #4.#16 // java/lang/Object."<init>":()V
    #2 = Fieldref #3.#17 // top/caolizhi/example/jvm/bytecode/Bytecode.s:I
    #3 = Class #18 // top/caolizhi/example/jvm/bytecode/Bytecode
    #4 = Class #19 // java/lang/Object
    #5 = Utf8 s
    #6 = Utf8 I
    #7 = Utf8 <init>
    #8 = Utf8 ()V
    #9 = Utf8 Code
    #10 = Utf8 LineNumberTable
    #11 = Utf8 LocalVariableTable
    #12 = Utf8 this
    #13 = Utf8 Ltop/caolizhi/example/jvm/bytecode/Bytecode;
    #14 = Utf8 SourceFile
    #15 = Utf8 Bytecode.java
    #16 = NameAndType #7:#8 // "<init>":()V
    #17 = NameAndType #5:#6 // s:I
    #18 = Utf8 top/caolizhi/example/jvm/bytecode/Bytecode
    #19 = Utf8 java/lang/Object
    {
    int s;
    descriptor: I
    flags:

    public top.caolizhi.example.jvm.bytecode.Bytecode();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
    stack=2, locals=1, args_size=1
    0: aload_0
    1: invokespecial #1 // Method java/lang/Object."<init>":()V
    4: aload_0
    5: iconst_3
    6: putfield #2 // Field s:I
    9: return
    LineNumberTable:
    line 3: 0
    line 5: 4
    LocalVariableTable:
    Start Length Slot Name Signature
    0 10 0 this Ltop/caolizhi/example/jvm/bytecode/Bytecode;
    }
    SourceFile: "Bytecode.java"

    上面这些都是工具帮你把 class 美化了的,但是 JVM 没法这么读,只能一个一个字节的读,接下来,我们从 JVM 的角度去读一下字节码。在 target > classes目录,找到 Bytecode.class 的文件,右键,选中 Open As Binary,class 文件的二进制如下:
    class 文件二进制数据
    接下来,开始一个一个字节的去读,看看 JVM 到底是怎么解析的?

如果我是一个虚拟机,我怎么读 class 文件?

这个 ClassFormat 文件谁规定的,当然是JVM 规范啦~。参考 Java Virtual Machine Specification

  • 读取魔数(magic

    魔数
    1
    u4  magic;

    就跟公司周三早餐铁打的紫菜饭团一样,魔数也是万年不变的 oxCAFEBABE,主要就是看着这个文件流是不是 class 文件。关于这个魔数的由来,可以去看看维基百科上 Java 始祖 James Gosling 说过的话 。也就是说,前面的 4 个字节确定是 CAFEBABE

    16 进制里面的只有 A ~ F,cafe 刚好属于 16 进制里面的,又与 java 渊源已久,另外 babe 推测想用 baby 的,毕竟 babe 比较轻浮口语化,可是 y 不在 16 进制的字母里面,所以改成了 e。

    class 文件字节的确也是这样:
    魔数

  • 读取版本号(minor_version,major_version

    版本号
    1
    2
    u2  minor_version;
    u2 major_version;

    接下来的4个字节是关于版本号的,前面的两个字节表示副版本,后面的两个字节表示主版本,比如这里:minor_version值为 0x0000,十进制就是 0major_version值为 0x0034,十进制就是 52,而 52 对应的是 jdk8,那么这个 class 文件的格式版本号就确定为:52.0,如果在 jdk11 的环境里面解析这个 class 文件是不被支持的。

    jdk 版本对应关系:

    jdk1.8 jdk9 jdk10 jdk11
    52 53 54 55

    class 文件字节:
    版本号

  • 解析常量池constant_pool很重要!很重要!很重要!

    常量池
    1
    2
    u2           constant_pool_count;
    cp_info constant_pool[constant_pool_count-1];

    接下来是非常重要的一部分了,后面的解析都会依赖常量池里面的值。先看版本号后面的两个字节,值为0x0014,十进制为 20,也就是说这个类的常量池表结构constant_pool数组大小为 19,0 位置保留,数组的每一个项都是cp_info 结构 ,如下:

    常量池表中项的结构 cp_info
    1
    2
    3
    4
    cp_info {
    u1 tag;
    u1 info[];
    }

    其中 tag 是个枚举值,info 根据 tag的值来确定结构,参考 JVM 规范文档

    常量池第 1 项:
    先来看看常量池表的第一个项的 tag0x0A,十进制为 10,如下图:
    constant_pool[1]
    tag的值在表中找到对应的 info 结构为 CONSTANT_Methodref_info,表示,类中方法的符号引用,它的结构如下:

    tag 为 10 对应的 info 结构
    1
    2
    3
    4
    5
    CONSTANT_Methodref_info {
    u1 tag; // 10
    u2 class_index; // 指向声明方法的类描述符 CONSTANT_Class_info 的索引项
    u2 name_and_type_index; // 指向名称以及类型描述符CONSTANT_NameAndType_info 的索引项
    }

    也就是说,在常量池表的第一项中,对应的就是 CONSTANT_Methodref_info 结构,已经知道 tag 是 10 了,那么 0x0A 后面的两个字节 0x0004,十进制 4,表示的是 u2 class_index,指向常量池的索引 4,我们看到常量池 4 位置的值为:
    声明方法的类

    再后面的两个字节 0x0010,十进制是 16,表示的是 u2 name_and_type_index,指向常量池索引 16,看 16 位置的值为:
    常量池第 1 项方法名称和返回类型

    常量池第 2 项:
    按照上面的方法,接着看的 tag0x09 对应 CONSTANT_Fieldref_info
    结构如下:

    tag 为 9 对应的 info 结构
    1
    2
    3
    4
    5
    CONSTANT_Fieldref_info {
    u1 tag; // 9
    u2 class_index; // 指向声明字段的类或接口描述符 CONSTANT_Class_info 的索引项
    u2 name_and_type_index; // 指向字段描述符CONSTANT_NameAndType_info 的索引项
    }

    u2 class_index -> 0x0003,十进制 3,指向常量池第 3 个位置:<top/caolizhi/example/jvm/bytecode/Bytecode>
    u2 name_and_type_index -> 0x0011,十进制 17,指向常量池 17 的位置:
    常量池第 2 项方法名称和返回类型

    常量池第 3 项:
    tag -> 0x07,十进制7,对应 CONSTANT_Class_info

    tag 为 7 对应的 info 结构
    1
    2
    3
    4
    CONSTANT_Class_info {
    u1 tag; // 7
    u2 name_index; // 全限定名常量项的索引
    }

    u2 name_index -> 0x0012,十进制 18,指向常量池 18 的位置:
    常量池第 3 项 name index

    常量池第 4 项:
    tag -> 0x07,十进制7,对应 CONSTANT_Class_info
    u2 name_index -> 0x0013,十进制 19,指向常量池 19 的位置:
    常量池第 4 项 name index

    常量池第 5 项:
    tag -> 0x01,十进制1,对应 CONSTANT_Utf8_info

    tag 为 1 对应的 info 结构
    1
    2
    3
    4
    5
    CONSTANT_Utf8_info {
    u1 tag; // 1
    u2 length; // UTF-8 缩略编码字符串占用字节数
    u1 bytes[length]; // 长度为 length 的 UTF-8 编码字符串
    }

    0x01 后面的两个字节 0x0001 表示 length,值为 1,也就是说接下来的后面 1 一个长度的字节是 UTF-8 编码字符串,即 0x73,ASCII 码表示 s,其实就是类中定义的字段 s,如图:
    常量池第 5 项

    常量池第 6 项:
    tag -> 0x01,十进制1,对应 CONSTANT_Utf8_info
    length -> 0x0001,十进制1,长度为1
    bytes -> 0x49,对应 ASCII 码 I,字段 s 的类型
    常量池第 6 项.png

    常量池第 7 项:
    tag -> 0x01,十进制1,对应 CONSTANT_Utf8_info
    length -> 0x0006,十进制 6,长度为6
    bytes -> 字节:3C 69 6E 69 74 3E,对应 ASCII 码的 <init>
    常量池第7项

    常量池第 8 项:
    tag -> 0x01,十进制1,对应 CONSTANT_Utf8_info
    length -> 0x0003,十进制 3,长度为3
    bytes -> 字节:28 29 56,对应 ASCII 码的 ()V
    常量池第 8 项

    常量池第 9 项:
    tag -> 0x01,十进制1,对应 CONSTANT_Utf8_info
    length -> 0x0004,十进制 4,长度为4
    bytes -> 字节:43 6F 64 65,对应 ASCII 码的 Code
    常量池第 9 项

    常量池第 10 项:
    tag -> 0x01,十进制1,对应 CONSTANT_Utf8_info
    length -> 0x000F,十进制 15,长度为15
    bytes -> 字节:4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65,对应 ASCII 码的 LineNumberTable
    常量池第 10 项

    常量池第 11 项:
    tag -> 0x01,十进制1,对应 CONSTANT_Utf8_info
    length -> 0x0012,十进制 18,长度为18
    bytes -> 字节:4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65,对应 ASCII 码的 LocalVariableTable
    常量池第 11 项

    常量池第 12 项:
    tag -> 0x01,十进制1,对应 CONSTANT_Utf8_info
    length -> 0x0004,十进制 4,长度为 4
    bytes -> 字节:74 68 69 73,对应 ASCII 码的 this
    常量池第 12 项

    常量池第 13 项:
    tag -> 0x01,十进制1,对应 CONSTANT_Utf8_info
    length -> 0x002C,十进制 44,长度为 44
    bytes -> 字节:4C 74 6F 70 2F 63 61 6F 6C 69 7A 68 69 2F 65 78 61 6D 70 6C 65 2F 6A 76 6D 2F 62 79 74 65 63 6F 64 65 2F 42 79 74 65 63 6F 64 65 3B,对应 ASCII 码的 Ltop/caolizhi/example/jvm/bytecode/Bytecode;
    常量池第 13 项

    常量池第 14 项:
    tag -> 0x01,十进制1,对应 CONSTANT_Utf8_info
    length -> 0x000A,十进制 10,长度为 10
    bytes -> 字节:0A 53 6F 75 72 63 65 46 69 6C 65,对应 ASCII 码的 SourceFile
    常量池第 14 项

    常量池第 15 项:
    tag -> 0x01,十进制1,对应 CONSTANT_Utf8_info
    length -> 0x000D,十进制 13,长度为 13
    bytes -> 字节:42 79 74 65 63 6F 64 65 2E 6A 61 76 61,对应 ASCII 码的 Bytecode.java
    常量池第 15 项

    常量池第 16 项:
    tag -> 0x0C,十进制 12,对应 CONSTANT_NameAndType_info

    tag 为 12 对应的 info 结构
    1
    2
    3
    4
    5
    CONSTANT_NameAndType_info {
    u1 tag; // 12
    u2 name_index; // 指向该字段或方法名称常量项的索引
    u2 descriptor_index; // 指向该字段或方法描述符常量项的索引
    }

    name_index -> 0x0007,十进制 7,指向常量池 7 的位置:
    常量池第 16 项 name index

descriptor_index -> 0x0008,十进制 8,指向常量池 8 的位置:
常量池第 16 项 descriptor index

常量池第 17 项:
tag -> 0x0C,十进制 12,对应 CONSTANT_NameAndType_info
name_index -> 0x0005,十进制 5,指向常量池 5 的位置:
常量池第 17 项 name index

descriptor_index -> 0x0006,十进制 6,指向常量池 6 的位置:
常量池第 17 项 descriptor index

常量池第 18 项:
tag -> 0x01,十进制1,对应 CONSTANT_Utf8_info
length -> 0x002A,十进制 42,长度为 42
bytes -> 字节:74 6F 70 2F 63 61 6F 6C 69 7A 68 69 2F 65 78 61 6D 70 6C 65 2F 6A 76 6D 2F 62 79 74 65 63 6F 64 65 2F 42 79 74 65 63 6F 64 65,对应 ASCII 码的 top/caolizhi/example/jvm/bytecode/Bytecode
常量池第 18 项

常量池第 19 项:
tag -> 0x01,十进制1,对应 CONSTANT_Utf8_info
length -> 0x0010,十进制 16,长度为 16,
bytes -> 字节:6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74,对应 ASCII 码的 java/lang/Object
常量池第 19 项

以上就是常量池的解析。

  • 解析类的访问权限(access_flags

    access_flags
    1
    u2    access_flags;

    接下来的 2 个字节表示的是 access_flags,表示访问标志位,标注类或接口的访问信息,这里值为 0x0021查文档可知,既包括 ACC_PUBLIC(0x0001) 又包括 ACC_SUPER(0x0020),jdk1.2以后都有ACC_SUPER。现在这个类是 public 类型的。
    access_flags

  • 解析当前类名称(this_class

    this_class
    1
    u2    this_class;

    访问标志位后面的 2 个字节 0x0003 表示当前类的全限定名,即包名+类名,指向常量池中的索引值。对应常量池 3 的位置:
    this_class

  • 解析父类名称(super_class

    super_class
    1
    u2    super_class;

    this_class 访问标志位后面的 2 个字节 0x0004 表示当前类的父类全限定名,指向常量池中的索引值。对应常量池 4 的位置:
    super_class

    java.lang.Object 是所有类的父类。

  • 解析接口(interfaces

    接口项
    1
    2
    u2         interfaces_count;
    u2 interfaces[interfaces_count];

    接下来的 2 个字节 0x0000,十进制 0,没有接口,也就没有 interfaces[]表结构。
    interfaces

  • 解析字段(fields

    字段项
    1
    2
    u2             fields_count;
    field_info fields[fields_count];

    字段项的前两个字节表示长度,0x0001,十进制 1,只有一个字段,和源码中定义一个字段匹配。field_info 结构如下:

    field_info 结构
    1
    2
    3
    4
    5
    6
    7
    field_info {
    u2 access_flags;
    u2 name_index;
    u2 descriptor_index;
    u2 attributes_count;
    attribute_info attributes[attributes_count];
    }

    fields_count 字节后面 2 个字节表示字段的访问标志,
    access_flags -> 0x0001 表示 public各个值对应表参考
    name_index -> 0x0005,指向常量池 5 的位置:s
    字段 name index
    descriptor_index -> 0x0006,指向常量池 6 的位置:I,有关字段类型参考链接
    字段 descriptor index

    attributes_count -> 0x0000,没有属性值。

    至此,字段的解析完毕。

  • 解析方法(methods

    方法解析
    1
    2
    u2             methods_count;
    method_info methods[methods_count];

    方法的解析和字段解析查不多,method_info 结构如下:

    method_info 结构
    1
    2
    3
    4
    5
    6
    7
    method_info {
    u2 access_flags;
    u2 name_index;
    u2 descriptor_index;
    u2 attributes_count;
    attribute_info attributes[attributes_count];
    }

    先看 methods_count 值为:0x0001,只有一个方法,这里奇怪了,我们没有定义方法,但是为什么会有一个方法呢?
    methods count

    继续往下看:
    access_flags -> 0x0001,表示 public,可以参考链接

    name_index -> 0x0007,表示方法名称,指向常量池的第 7 个索引位置值,<init>
    方法名称

    descriptor_index -> 0x0008,方法描述符,指向常量池的第 8 个索引位置值,()V
    方法描述符

    attributes_count -> 0x0001,说明有一个属性,属性的结构:

    attribute_info 结构
    1
    2
    3
    4
    5
    attribute_info {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 info[attribute_length];
    }

    接下来的 2 个字节 0x0009 表示 attribute_name_index,指向常量池的索引值 9。
    方法中的属性值

    attribute_info中的info 结构类型有很多,可以参考链接 。这里的类型为 Code

    attribute_length -> 占 4 个字节,0x00_00_00_38,十进制为 56,表示属性长度为 56,也就是这个字节后面的 56 个字节表示的是 Code 这个属性。不包括前面 attribute_name_indexattribute_length 占用的 6 个字节。
    方法中的 name index 和 length

    接下来看一下 Code 属性的结构,Code 属性是一个 class 文件最重要的一个属性!

    Code 属性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 max_stack; // 操作数栈深度最大值
    u2 max_locals; // 局部变量表所需要存储的空间,单位是 slot,
    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]; // 属性表
    }

    max_stack -> 0x0002,表示操作数栈最大深度是 2,这里可以思考一下为什么是 2 ?
    最大操作数栈

    max_locals -> 0x0001,表示局部变量表的大小是 1,占用一个 slot。可以看到编译后就已经确定了一个方法的大小。
    局部变量表大小

    code_length -> 00 00 00 0A,十进制 10,也就是说后面的 10 个字节就是这个方法的字节码指令。
    字节码指令大小

    code -> 2A B7 00 01 2A 06 B5 00 02 B1,这 10 个字节就是一组字节码指令,在 JVM 中一个字节表示一个指令,最大只有 255 个指令集。
    字节码指令

    查看JVM的指令集表,上面的 10 个字节中的字节码指令如下:

    操作码 HEX 含义
    aload_0 0x2A 将局部变量表中的第 0 个 slot 中 reference 类型的本地变量推送到操作数栈顶
    invokespecial 0xB7 调用类的实例构造方法、private 方法或父类方法,指令后面会带上一个 u2 类型的参数,指向常量池的索引值,表示调用哪个方法
    iconst_3 0x06 将 int 类型 3 推到栈顶
    putfield 0xB5 用栈顶的值为指定的实例域赋值,指令后面会带上一个 u2 类型的参数,表示给哪一个赋值,指向常量池的索引值
    return 0xB1 从当前方法返回

    上面的字节码执行流程如下:
    字节码

    exception_table_length -> 0x0000,异常表大小是 0:
    异常表属性

    attributes_count -> 0x0002Code 属性的属性表大小是 2 个,
    Code 属性表大小

    属性表结构都是 attribute_info,如下:

    attribute_info 结构
    1
    2
    3
    4
    5
    attribute_info {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 info[attribute_length];
    }

    Code 属性的第 1 个属性:

    attribute_name_index -> 0x000A,十进制是 10,指向常量池的索引值LineNumberTable
    LineNumberTable 属性

    LineNumberTable 属性用于记录字节码指令与源代码行号对应的关系,异常抛出时显示行号,debug 调试时用到的行号,结构:

    LineNumberTable 结构
    1
    2
    3
    4
    5
    6
    7
    8
    9

    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 -> 00 00 00 0A,十进制 10,属性的长度为 10,即后面的 10 个字节描述这个属性。

    line_number_table_length -> 0x0002,行号表长度为 2,

    • 行号表第 1 行
      start_pc -> 0x0000,字节码行号,也即字节码指令偏移位置
      line_number -> 0x0003,Java源码行号
      字节码和源码行号对应关系1
    • 行号表第 2 行
      start_pc -> 0x0004
      line_number -> 0x0005
      字节码和源码行号对应关系2

    Code 属性的第 2 个属性:

    attribute_name_index -> 0x000B,十进制是 11,指向常量池的索引值LocalVariableTable
    LocalVariableTable 属性

    LocalVariableTable 属性用于描述栈桢中局部变量表中的变量与 Java 源码中定义的变量之间的关系,结构:

    LocalVariableTable 结构
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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; // 栈桢中局部变量表中 slot 的位置
    } local_variable_table[local_variable_table_length];
    }

    attribute_length -> 00 00 00 0C,十进制 12,属性的长度为 12,即后面的 12 个字节描述这个属性。

    local_variable_table_length -> 0x0001,局部变量表长度为 1,
    start_pc -> 0x0000,字节码行号,即 aload_0
    length -> 0x000A,长度为 10,和上面的结合起来就是这个变量在字节码中的作用域范围,10个字节,从字节码偏移量 0 开始 到 10 结束,如下图,可以看拿到覆盖了整个 <init> 方法。
    变量作用域

    name_index -> 0x000C,指向常量池 12 的位置
    LocalVariableTable表的第1个变量的 name index

    descriptor_index -> 0x000D,指向常量池 13 的位置
    LocalVariableTable表的第1个变量的descriptor index

    也就是说 this 指向的对象类型是 Bytecode

    index -> 0x0000,理所当然,在局部变量表的第 0 个 slot 位置。

    至此,整个方法解析已经完成了

  • 解析属性(attributes

    属性
    1
    2
    u2               attributes_count;
    attribute_info attributes[attributes_count];

    接下来的 2 个字节表示了这个类的属性大小,即 attributes_count -> 0x0001
    类属性的大小

    attribute_info 跟上面的方法的属性类型结构都是相同的,具体类型见链接

    attribute_name_index -> 0x000E,指向常量池索引 14 的位置 SourceFile
    SourceFile属性

    SourceFile 属性就是记录生成这个 Class 文件的源码文件名称。一般来说,类名和文件名是一致的,除了少数类比如内部类,抛出异常时可以显示错误所属的文件名。

    SourceFile 属性结构
    1
    2
    3
    4
    5
    SourceFile_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 sourcefile_index;
    }

    attribute_length -> 00 00 00 02,长度是 2,后面的 2 个字节描述的是该属性。

    sourcefile_index -> 0x000F,十进制 15,指向常量池的一个索引值 Bytecode.java
    SourceFile属性的 sourcefile index

    至此,该类的属性解析完了



至此,该类解析完了!!!没有多余的字节!

啰嗦一句,解析完了我该干嘛?

解析完了,我把他存起来,下次用到的时候,我再开箱即用,放哪里好呢,放到内存里面,那这个只是我能用,java 应用他们咋用,嗯,我再复制一份给它用,我的是我的,它的还是我的,哈哈~!

作者

操先森

发布于

2021-12-09

更新于

2021-12-13

许可协议

评论