跳至主要內容

二、字节码指令篇

安图新大约 6 分钟

二、字节码指令篇

一、Class 文件结构

Class 文件的结构并不是一成不变的,随着 Java 虚拟机的不断发展,总是不可避免地会对 Class 文件结构做出一些调整,但是其基本结构和框架是非常稳定的。

  • 魔数
  • Class 文件版本
  • 常量池
  • 访问标识(或标志)
  • 类索引,父类索引,接口索引集合
  • 字段表集合
  • 方法表集合
  • 属性表集合

![ ][nbsp]

1、魔数

  • 每个 Class 文件的头 4 个字节称为“魔数(Magic Number)”。
  • 它的唯一作用是确定这个文件是否为一个能被虚拟机所接受的有效、合法的 class 文件。
  • class 文件的魔数值固定为 OxCAFEBABE,不会改变。

2、class 文件版本

  • 排列在 magic 后的第 5 个和第 6 个字节所代表的含义就是编译的副版本号 minor_version,而第 7 个和第 8 个字节就是编译的主版本号 major_version。
  • 验证字节码文件的主版本号和次版本号同样也是格式验证的任务之一,因为如果是高版本的 JDK 编译的字节码文件,自然不能在低版本的 JVM 中运行,否则 JVM 会抛出 java.lang.UnsupportedClassVersionError 异常。

3、常量池

1、 constant_pool_count(常量池计数器);
constant_pool_count 的值等于常量池表中的成员数加 1。常量池表的索引值只有在大于 0 且小于 constant_pool_count 时才会认为是有效的,对于 long 和 double 类型有例外情况。

2、 constant_pool[](常量池);
constant_pool 是一种表结构,以 1 ~ constant_pool_count - 1 为索引。
它包含 class 文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其他常量。常量池中的每一项都具备相同的特征——第 1 个字节作为类型标记,用于确定该项的格式,这个字节称为 tag byte (标记字节、标签字节)。

4、访问标志

常量池后面就是访问标志,用两个字节来表示,其标识了类或者接口的访问信息,比如:该 Class 文件是类还是接口,是否被定义成 public,是否是 abstract,如果是类,是否被声明成 final 等等。各种访问标志如下所示:

![ ][nbsp 1]

5、类索引、父类索引、接口索引集合

  • 访问标志后的两个字节就是类索引;
  • 类索引后的两个字节就是父类索引;
  • 父类索引后的两个字节则是接口索引计数器。
  • 通过这三项,就可以确定了这个类的继承关系了。

6、字段表集合

字段表用来描述类或者接口中声明的变量。这里的字段包含了类级别变量以及实例变量,但是不包括方法内部声明的局部变量。

fields:指向常量池索引集合,它完整描述了每个字段。

  • fields_count (字段计数器)
    fields_count 的值表示当前 class 文件 fields 表的成员个数。fields 表中每个成员都是一个 field_info 结构,用于表示该类或接口所声明的类字段或者实例字段。
  • fields [](字段表)
    fields 表中的每个成员都必须是一个 fields_info 结构的数据项,用 于表示当前类或接口中某个字段的完整描述。fields 表描述当前类或接口声明的所有字段,但不包括从父类或父接口继承的那些字段。

字段表访问标志:

![ ][nbsp 2]

7、方法表集合

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 表只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。

方法表访问标志:

![ ][nbsp 3]

8、属性表集合

属性表不仅在方法表有用到,字段表和 Class 文件中也会用得到。

attributes:不同值的集合,它提供了额外的关于这个类的信息,包括任何带有 RetentionPolicy.CLASS 或者 RetentionPolicy.RUNTIME 的注解。

  • attributes_count (属性计数器)
    attributes_count 的值表示当前 class 文件属性表的成员个数。属性表中每一项都是一个 attribute_info 结构。
  • attributes [](属性表)
    属性表的每个项的值必须是 attribute_info 结构。

二、字节码指令集

JVM 中字节码指令集按用途大致可分为 9 类:

1、 加载与存储指令;
2、 算术指令;
3、 类型转换指令;
4、 对象的创建与访问指令;
5、 方法调用与返回指令;
6、 操作数栈管理指令;
7、 控制转移指令;
8、 异常处理指令;
9、 同步控制指令;

在做值相关操作时:

  • 一个指令,可以从局部变量表、常量池、堆中对象、方法调用、系统调用中等取得数据,这些数据(可能是值,可能是对象的引用)被压入操作数栈。
  • 一个指令,也可以从操作数栈中取出一到多个值(pop 多次),完成赋值、加减乘除、方法传参、系统调用等等操作。

1、方法调用指令

  • invokevirtual 指令:用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),支持多态。这也是 Java 语言中最常见的方法分派方式。
  • invokeinterface 指令:用于调用接口方法,它会在运行时搜索由特定对象所实现的这个接口方法,并找出适合的方法进行调用。
  • invokespecial 指令:用于调用一些需要特殊处理的实例方法,包括实例初始化方法(构造器)、私有方法和父类方法。这些方法都是静态类型绑定的,不会在调用时进行动态派发。
  • invokestatic 指令:用于调用命名类中的类方法(static 方法)。这是静态绑定的。
  • invokedynamic 指令:调用动态绑定的方法,这个是 JDK 1.7 后新加入的指令。用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法。前面 4 条调用指令的分派逻辑都固化在 java 虚拟机内部,而 invokedynamic 指令的分派逻辑是由用户所设定的引导方法决定的。l [nbsp]: https://cdn.hotmindshare.com/custom/images/2024/2/19/1943/1708343035526.png [nbsp 1]: https://cdn.hotmindshare.com/custom/images/2024/2/19/1943/1708343035657.png [nbsp 2]: https://cdn.hotmindshare.com/custom/images/2024/2/19/1943/1708343035773.png [nbsp 3]: https://cdn.hotmindshare.com/custom/images/2024/2/19/1943/1708343035889.png
上次编辑于:
贡献者: Andy