字节码指令
# 字节码指令
# Class 文件结构


# 解析方式
# 一个个字节分析
使用 NotePad++ 或 Binary Viewer 等工具查看 16 进制编码

# javap 指令

# IDEA 插件
jclasslib 或客户端



# 文件结构
| 类型 | 名称 | 说明 | 长度 | 数量 |
|---|---|---|---|---|
| u4 | magic | 魔数,识别 Class. 文件格式 | 4 个字节 | 1 |
| u2 | minor_version | 副版本号 | 2 个字节 | 1 |
| u2 | major_version | 主版本号 | 2 个字节 | 1 |
| u2 | constant_pool_count | 常量池计算器 | 2 个字节 | 1 |
| cp_info | constant_pool | 常量池 | n 个字节 | constant_pool_count-1 |
| u2 | access_flags | 访问标志 | 2 个字节 | 1 |
| u2 | this_class | 类索引 | 2 个字节 | 1 |
| u2 | super_class | 父类索引 | 2 个字节 | 1 |
| u2 | interfaces_count | 接口计数器 | 2 个字节 | 1 |
| u2 | interfaces | 接口索引集合 | 2 个字节 | interfaces count |
| u2 | fields_count | 字段个数 | 2 个字节 | 1 |
| field_info | fields | 字段集合 | n 个字节 | fields_count |
| u2 | methods_count | 方法计数器 | 2 个字节 | 1 |
| method_info | methods | 方法集合 | n 个字节 | methods count |
| u2 | attributes_count | 附加属性计数器 | 2 个字节 | 1 |
| attribute_info | attributes | 附加属性集合 | n 个字节 | attributes_count |
即:
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];
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 魔数
Magic (魔数)
- 每个 Class 文件的头 4 个字节称为 “魔数(Magic Number)”
- 它的唯一作用是确定这个文件是否为一个能被虚拟机所接受的有效、合法的 class 文件。
- class 文件的魔数值固定为 OxCAFEBABE,不会改变。
# class 文件版本
版本号
- 排列在 magic 后的第 5 个和第 6 个字节所代表的含义就是编译的副版本号 minor_version,而第 7 个和第 8 个字节就是编译的主版本号 major_version。
- 验证字节码文件的主版本号和次版本号同样也是格式验证的任务之一,因为如果是高版本的 JDK 编译的字节码文件,自然不能在低版本的 JVM 中运行,否则 JVM 会抛出 java.lang.UnsupportedClassVersionError 异常。
# 常量池
常量池(constant_pool)
和符号表类似,详情请看下面的 “运行时常量池”
- constant_pool_count (常量池计数器):constant_pool_count 的值等于常量池表中的成员数加 1。常量池表的索引值只有在大于 0 且小于 constant_pool_count 时才会认为是有效的,对于 long 和 double 类型有例外情况。
- constant_pool [](常量池):constant_pool 是一种表结构,以 1 ~ constant_pool_count - 1 为索引。它包含 class 文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其他常量。常量池中的每一项都具备相同的特征 —— 第 1 个字节作为类型标记,用于确定该项的格式,这个字节称为 tag byte (标记字节、标签字节)。
# 访问标志
常量池后面就是访问标志,用两个字节来表示,其标识了类或者接口的访问信息,比如:该 Class 文件是类还是接口,是否被定义成 public,是否是 abstract,如果是类,是否被声明成 final 等等。各种访问标志如下所示:
| 标志名称 | 标志值 | 含义 |
|---|---|---|
| ACC_PUBLIC | 0x0001 | 是否为 Publica 类型 |
| ACC_FINAL | 0x0010 | 是否被声明为 final, 只有类可以设置 |
| ACC_SUPER | 0x0020 | 是否允许使用 invokespecial 字节码指令的新语义,JDK1.0.,2 之后编译出来的类的这个标志默认为真 |
| ACC_INTERFACE | 0x0200 | 标志这是一个接口 |
| ACC_ABSTRACT | 0x0400 | 是否为 abstracta 类型,对于接口或者抽象类来说,次标志值为真,其他类型为假 |
| ACC _SYNTHETIC | 0x1000 | 标志这个类并非由用户代码产生 |
| ACC_ANNOTATION | 0x2000 | 标志这是一个注解 |
| ACC_ENUM | ×4000 | 标志这是一个枚举 |
# 类索引、父类索引、接口索引集合
类索引、父类索引、接口索引
- 访问标志后的两个字节就是类索引;
- 类索引后的两个字节就是父类索引;
- 父类索引后的两个字节则是接口索引计数器。
通过这三项,就可以确定了这个类的继承关系了。
# 字段表集合
字段表用来描述类或者接口中声明的变量。这里的字段包含了类级别变量以及实例变量,但是不包括方法内部声明的局部变量。
fields:指向常量池索引集合,它完整描述了每个字段。
- fields_count (字段计数器):fields_count 的值表示当前 class 文件 fields 表的成员个数。fields 表中每个成员都是一个 field_info 结构,用于表示该类或接口所声明的类字段或者实例字段。
- fields [](字段表):fields 表中的每个成员都必须是一个 fields_info 结构的数据项,用 于表示当前类或接口中某个字段的完整描述。fields 表描述当前类或接口声明的所有字段,但不包括从父类或父接口继承的那些字段。
字段表访问标志:
| 标志名称 | 标志值 | 含义 |
|---|---|---|
| ACC_PUBLIC | 0x0001 | 字段是否为 public |
| ACC_PRIVATE | 0x0002 | 字段是否为 private |
| ACC_PROTECTED | 0x0004 | 字段是否为 protected |
| ACC_STATIC | 0x0008 | 字段是否为 static |
| ACC_FINAL | 0x0010 | 字段是否为 final |
| ACC_VOLATILE | 0x0040 | 字段是否为 volatile |
| ACC_TRANSTENT | 0x0080 | 字段是否为 transient |
| ACC_SYNCHETIC | 0x1000 | 字段是否为由编译器自动产生 |
| ACC_ENUM | 0x4000 | 字段是否为 enum |
# 方法表集合
methods:指向常量池索引集合,它完整描述了每个方法的签名,如果这个方法不是抽象的或者不是 native 的,那么字节码中会体现出来。
- methods_count (方法计数器):methods_count 的值表示当前 class 文件 methods 表的成员个数。methods 表中每个成员都是一个 method_info 结构。
- methods [](方法表):methods 表中的每个成员都必须是一个 method_info 结构,用于表示当前类或接口中某个方法的完整描述。如果某个 method_info 结构的 access_ flags 项既没有设置 ACC_NATIVE 标志也没有设置 ACC_ABSTRACT 标志,那么该 结构中也应包含实现这个方法所用的 Java 虚拟机指令。
method_info 结构可以表示类和接口中定义的所有方法,包括实例方法、类方法、 实例初始化方法和类或接口初始化方法。methods 表只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。
方法表访问标志:
| 标志名称 | 标志值 | 含义 |
|---|---|---|
| ACC_PUBLIC | 0x0001 | 方法是否为 oublic |
| ACC_PRIVATE | 0x0002 | 方法是否为 private |
| ACC_PROTECTED | 0x0004 | 方法是否为 protected |
| ACC_STATIC | 0x0008 | 方法是否为 static |
| ACC_FINAL | 0x0010 | 方法是否为 final |
| ACC_SYHCHRONRIZED | 0x0020 | 方法是否为 synchronized |
| ACC_BRIDGE | 0x0040 | 方法是否是有编译器产生的方法 |
| ACC_VARARGS | 0x0080 | 方法是否接受参数 |
| ACC_NATIVE | 0x0100 | 方法是否为 native |
| ACC_ABSTRACT | 0x0400 | 方法是否为 abstract |
| ACC_STRICTFP | 0x0800 | 方法是否为 strictfp |
| ACC_SYNTHETIC | 0x1000 | 方法是否是有编泽器自动产生的 |
方法表结构:
| 类型 | 名称 | 含义 | 数量 |
|---|---|---|---|
| u2 | access_flags | 访问标志 | 1 |
| u2 | name_index | 方法名索引 | 1 |
| u2 | descriptor_index | 描述符索引 | 1 |
| u2 | attributes count | 属性计数器 | 1 |
| attribute_info | attributes | 属性集合 | attributes count |
# 属性表集合
前面说到了属性表,现在来重点看下。属性表不仅在方法表有用到,字段表和 Class 文件中也会用得到。本篇文章中用到的例子在字段表中的属性个数为 0,所以也没涉及到;在方法表中用到了 2 次,都是 Code 属性;至于 Class 文件,在末尾时会讲到,这里就先不说了。
attributes:不同值的集合,它提供了额外的关于这个类的信息,包括任何带有 RetentionPolicy.CLASS 或者 RetentionPolicy.RUNTIME 的注解。
- attributes_count (属性计数器):attributes_count 的值表示当前 class 文件属性表的成员个数。属性表中每一项都是一个 attribute_info 结构。
- attributes [](属性表):属性表的每个项的值必须是 attribute_info 结构。
# 字节码指令集
见《JVM 指令手册》