JVM 字节码
Java 之所以可以“一次编译,到处运行”,一是因为 JVM 针对各种操作系统、平台都进行了定制,二是因为无论在什么平台,都可以编译生成固定格式的字节码(.class 文件)供 JVM 使用。
.class 文件是一组以 8 位字节为基础单位的二进制流,各个数据项严格按照顺序紧凑地排列在 .class 文件中,中间没有添加任何分隔符。整个 .class 文件本质上就是一张表。
字节码
什么是字节码
之所以被称之为字节码,是因为字节码文件由十六进制值组成,而 JVM 以两个十六进制值为一组,即以字节为单位进行读取。在 Java 中一般是用 javac
命令编译源代码为字节码文件,一个.java 文件从编译到运行的示例如下图所示。
对于开发人员,了解字节码可以更准确、直观地理解 Java 语言中更深层次的东西,比如通过字节码,可以很直观地看到 Volatile 关键字如何在字节码上生效。另外,字节码增强技术在 Spring AOP、各种 ORM 框架、热部署中的应用屡见不鲜,深入理解其原理对于我们来说大有裨益。除此之外,由于 JVM 规范的存在,只要最终可以生成符合规范的字节码就可以在 JVM 上运行,因此这就给了各种运行在 JVM 上的语言(如 Scala、Groovy、Kotlin)一种契机,可以扩展 Java 所没有的特性或者实现各种语法糖。理解字节码后再学习这些语言,可以“逆流而上”,从字节码视角看它的设计思路,学习起来也“易如反掌”。
字节码结构
.java 文件通过 javac 编译后将得到一个.class 文件
(1)魔数(Magic Number)
每个 .class
文件的头 4 个字节称为 魔数(magic number)
,它的唯一作用是确定这个文件是否为一个能被虚拟机接收的 .class
文件。魔数的固定值为:0xCAFEBABE
。
有趣的是,魔数的固定值是 Java 之父 James Gosling 制定的,为 CafeBabe(咖啡宝贝),而 Java 的图标为一杯咖啡。
(2)版本号(Version)
版本号为魔数之后的 4 个字节,前两个字节表示次版本号(Minor Version),后两个字节表示主版本号(Major Version)。
(3)常量池(Constant Pool)
紧接着主版本号之后的字节为常量池入口。
常量池主要存放两类常量:
- 字面量 - 如文本字符串、声明为
final
的常量值。 - 符号引用
- 类和接口的全限定名(Fully Qualified Name)
- 字段的名称和描述符(Descriptor)
- 方法的名称和描述符
常量池整体上分为两部分:常量池计数器以及常量池数据区:
- 常量池计数器(constant_pool_count) - 由于常量的数量不固定,所以需要先放置两个字节来表示常量池容量计数值。
- 常量池数据区 - 数据区是由(
constant_pool_count-1
)个 cp_info 结构组成,一个 cp_info 结构对应一个常量。
(4)访问标志
紧接着的 2 个字节代表访问标志(access_flags),这个标志用于识别一些类或者接口的访问信息,描述该 Class 是类还是接口,以及是否被 public
、abstract
、final
等修饰符修饰。
(5)当前类名
访问标志后的 2 个字节,描述的是当前类的全限定名。这两个字节保存的值为常量池中的索引值,根据索引值就能在常量池中找到这个类的全限定名。
(6)父类名称
当前类名后的 2 个字节,描述父类的全限定名,同上,保存的也是常量池中的索引值。
(7)接口信息
父类名称后为 2 字节的接口计数器,描述了该类或父类实现的接口数量。紧接着的 n 个字节是所有接口名称的字符串常量的索引值。
(8)字段表
字段表用于描述类和接口中声明的变量,包含类级别的变量以及实例变量,但是不包含方法内部声明的局部变量。字段表也分为两部分,第一部分为两个字节,描述字段个数;第二部分是每个字段的详细信息 fields_info。
(9)方法表
字段表结束后为方法表,方法表也是由两部分组成,第一部分为两个字节描述方法的个数;第二部分为每个方法的详细信息。方法的详细信息较为复杂,包括方法的访问标志、方法名、方法的描述符以及方法的属性
(10)附加属性表
字节码的最后一部分,该项存放了在该文件中类或接口所定义属性的基本信息。
属性名称 | 使用位置 | 含义 |
---|---|---|
Code | 方法表 | Java代码编译成的字节码指令 |
ConstantValue | 字段表 | final关键字定义的常量值 |
Deprecated | 类、方法表、字段表 | 被声明为deprecated的方法和字段 |
Exceptions | 方法表 | 方法排除的异常 |
InnerClass | 类文件 | 内部类列表 |
LineNumberTable | Code属性 | Java源代码的行号和字节码指令的对应关系 |
LocalVariableTable | Code属性 | 方法的局部变量描述 |
SourceFile | 类文件 | 源文件名称 |
Synthetic | 类、方法表、字段表 | 标识方法或字段为编译器自动生成的 |