首先,我们说说为什么会有Java语言的出现,相比于之前的C和C++,Java语言的优势和设计初衷是什么呢?

由于计算机操作系统的多样性,为了实现代码的一次编写,到处运行,Java语言将代码的编译和执行分隔开来,通过代码编译为.class文件后由JVM来进行执行,有过Java代码在dos界面的执行的盆友应该深有体会,首先通过Javac **.java 生成 **.class文件,然后通过Java***来执行.class 文件;只要我们为每一个操作系统设计出不同的JVM,那们,只要我们将自己的代码编译成符合规范的.class 文件,那么,我们就能实现跨平台(跨操作系统),跨语言(不用编程语言)的能力;

既然说JVM只能执行符合规范的.class 文件,那么,我们就很有必要来研究一下.class 文件的语言规范;

.class 字节码文件结构是一组以 8 位字节为基础单位的二进制流,各数据项目严格按照顺序紧凑地排列在.class 文件之中,中间没有添加任何分隔符。在字节码结构中,有两种最基本的数据类型来表示字节码文件格式,分别是:无符号数和表。

无符号数为最基本的数据类型,

表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以_info结尾。表用于描述有层次关系的复合结构的数据,整个.class 文件本质上就是一张表;

完整的 Class 字节码文件由以下几部分组成:

  1. 魔数与Class文件版本
  2. 常量池
  3. 访问标志
  4. 类索引、父类索引、接口索引集合
  5. 字段表集合
  6. 方法表集合
  7. 属性表集合

1.魔数与Class文件版本

Class 文件的前 4 个字节称为魔数(Magic Number),唯一的作用就是确定这个文件是否为一个能被虚拟机接受的 .class 文件,其值固定为:0xCAFEBABE(咖啡宝贝)。如果 .class 文件的魔数不是 0xCAFEBABE,那么虚拟机将拒绝运行这个.class文件。

.class 文件的第 4 - 5个字节代表了 .class 文件的次版本号(Minor Version),即编译该 .class 文件的 JDK 次版本号。

.class 文件的第 6-7 个字节代表了 .class 文件的主版本号(Major Version),即编译该 .class 文件的 JDK 主版本号。

高版本的 JDK 能向下兼容以前笨笨的 .class 文件,但不能运行新版本的 .class 文件。例如一个 .class 文件是使用 JDK 1.5 编译的,那么我们可以用 JDK 1.6 虚拟机运行它,但不能用 JDK 1.4 虚拟机运行它。

2.常量池

紧跟版本信息之后的是常量池信息,与文件中其他项目相关性最大的数据,也是占用空间的最大数据项之一;前面放置的是一个u2的数据,为常量池容量计数值(constant pool count),后面是具体的常量值;

常量池中主要存放两大类常量,字面量(Literal)和符号引用(Symbolic),字面量包括文本字符串,被声明为final的常量值等,符号引用包括类和接口的全限定名(Fully Qualified Name),字段的名称和描述符(Description),方法的名称和描述符;

常量池的每一项都是一个表,共有11种结构不同的表结构数据,其中每一种常量表结构包括一个u2 的标志位,代表当前常量的类型,之后就是每一种常量具体的内容,对于有UTF-8编码的字符串,用一个u2的数据表示字符串的长度,由于u2最大可以表示的数据大小为65535,因此,在Java种,定义的英文字符变量或方法名不能超过64K,否则,将无法完成编译;

访问标志

在常量池结束之后,紧接着的两个字节代表类或接口的访问标志(access_flags),有32个标志位可以使用;

标志用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口、是否定义为public类型、是否定义为abstract类型等。具体的标志位以及标志的含义见下表。




ResourcePatternResolver跨jar包读取文件 java跨文件夹调用class_为什么.class文件查看不了


类索引、父类索引、接口索引

在访问标记后,则是类索引、父类索引、接口索引的数据,类索引和父类索引都是一个u2类型的数据,而接口索引集合是一组u2类型的数据的集合,.class 文件中由这三项数据来确定这个类的继承关系。

类索引:类索引用于确定这个类的全限定名,它用一个 u2 类型的数据表示。

父类索引:父类索引用于确定这个类的父类的全限定名,父类索引用一个u2类型的数据表示。

接口索引:接口索引集合就用来描述哪个类实现了哪些接口,这些被实现的接口将按 implements 语句(如果这个类本身就是一个接口,则应当是extends语句)后的接口顺序从左到右排列在接口索引集合中。对于接口索引集合,入口第一项是 u2 类型的数据为接口计数器(interfaces_count),表示索引表的容量,而在接口计数器后则紧跟着所有的接口信息。如果该类没有实现任何接口,则该计数器值为0,后面接口的索引表不再占用任何字节。

字段表集合

字段表集合用于描述接口或者类中声明的变量,包括类级变量和实例级变量,但不包括在方法内部声明的局部变量。在类接口集合后的2个字节是一个字段计数器,表示属性字段的个数。在字段计数器后,才是具体的属性数据。

字段表的每个字段用一个名为 field_info 的表来表示,field_info 表的数据结构如下所示:


ResourcePatternResolver跨jar包读取文件 java跨文件夹调用class_字段_02


方法表集合

在字段表后的 2 个字节是方法计数器,表示类中总有有几个方法,在方法计数器后,才是具体的方法数据。

方法表中的每个方法都用一个 method_info 表示,其数据结构如下:


ResourcePatternResolver跨jar包读取文件 java跨文件夹调用class_为什么.class文件查看不了_03


属性表集合

在.class文件,字段表,方法表种都携带自己的属性表集合,用于描述某种场景特有的信息;属性表格式不再要求每个属性具有严格的顺序,任何人都可以往属性表中添加信息