什么是字节码?

Java之所以可以“一次编译,到处运行”,一是因为JVM针对各种操作系统、平台都进行了定制,二 是因为无论在什么平台,都可以编译生成固定格式的字节码(.class文件)供JVM使用。因此,也可以看 出字节码对于Java生态的重要性。之所以被称之为字节码,是因为字节码文件由十六进制值组成,而 JVM以两个十六进制值为一组,即以字节为单位进行读取。在Java中一般是用javac命令编译源代码为字 节码文件。

对于开发人员,了解字节码可以更准确、直观地理解Java语言中更深层次的东西,比如通过字节 码,可以很直观地看到Volatile关键字如何在字节码上生效。另外,字节码增强技术在Spring AOP、各 种ORM框架、热部署中的应用屡见不鲜,深入理解其原理对于我们来说大有裨益。除此之外,由于JVM 规范的存在,只要最终可以生成符合规范的字节码就可以在JVM上运行,因此这就给了各种运行在JVM 上的语言(如Scala、Groovy、Kotlin)一种契机,可以扩展Java所没有的特性或者实现各种语法

 JVM规定的字节码结构

魔数

版本号

常量池

访问标志

当前类索引

Magic Number

Version

Constant Pool

access  flag

this  class

父类索引

接口索引

字段表

方法表

附加属性

super class

interfaces

fields

methods

attributes

(1) 魔数(Magic Number) 所有的.class文件的前四个字节都是魔数,魔数的固定值为:0xCAFEBABE。魔数放在文件开头, JVM可以根据文件的开头来判断这个文件是否可能是一个.class文件,如果是,才会继续进行之后的操 作。

(2) 版本号 版本号为魔数之后的4个字节,前两个字节表示次版本号(Minor Version),后两个字节表示主版 本号(Major Version)。上图2中版本号为“00 00 00 34”,次版本号转化为十进制为0,主版本号转化 为十进制为52,在Oracle官网中查询序号52对应的主版本号为1.8,所以编译该文件的Java版本号为 1.8.0。

(3) 常量池(Constant Pool)紧接着主版本号之后的字节为常量池入口。常量池中存储两类常量:字面量与符号引用。字面量为 代码中声明为Final的常量值,符号引用如类和接口的全局限定名、字段的名称和描述符、方法的名称和 描述符。

(4) 访问标志 常量池结束之后的两个字节,描述该Class是类还是接口,以及是否被Public、Abstract、Final等修 饰符修饰。JVM规范规定了如下图9的访问标志(Access_Flag)。需要注意的是,JVM并没有穷举所有 的访问标志,而是使用按位或操作来进行描述的,比如某个类的修饰符为Public Final,则对应的访问修 饰符的值为ACC_PUBLIC | ACC_FINAL,即0x0001 | 0x0010=0x0011

(5) 当前类名 访问标志后的两个字节,描述的是当前类的全限定名。这两个字节保存的值为常量池中的索引值, 根据索引值就能在常量池中找到这个类的全限定名。

(6) 父类名称 当前类名后的两个字节,描述父类的全限定名,同上,保存的也是常量池中的索引值。

(7) 接口信息 父类名称后为两字节的接口计数器,描述了该类或父类实现的接口数量。紧接着的n个字节是所有 接口名称的字符串常量的索引值。

(8) 字段表 字段表用于描述类和接口中声明的变量,包含类级别的变量以及实例变量,但是不包含方法内部声 明的局部变量。字段表也分为两部分,第一部分为两个字节,描述字段个数;第二部分是每个字段的详 细信息fields_info。

(9)方法表 字段表结束后为方法表,方法表也是由两部分组成,第一部分为两个字节描述方法的个数;第二部 分为每个方法的详细信息。方法的详细信息较为复杂,包括方法的访问标志、方法名、方法的描述符以 及方法的属性,

(10)附加属性表 字节码的最后一部分,该项存放了在该文件中类或接口所定义属性的基本信息。