JAVA实现平台无关性的基础是虚拟机和字节码存储格式,使用Java编译器可以把Java代码编译为存储字节码的Class文件,使用JRuby等其他语言的编译器一样可以把程序代码编译成Class文件,虚拟机并不关心Class的来源是什么语言,只要它符合Class文件应有的结构就可以在Java虚拟机中运行。
Java语言中的各种变量、关键字和运算符号的语义最终都是由多条字节码命令组合而成的,因此字节码命令所能提供的语义描述能力肯定比Java语言本身更强大。Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件存储的内容几乎全部都是程序运行的必要数据,没有空隙。当遇到需要占用8位字节以上空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储。
根据Java虚拟机规范的规定,Class文件格式采用一种类似于C语言结构体的伪结构表示。与C语言结构体的域不同,连续的项Java Class文件中顺序存储,不进行填充或者对齐。在《JVM Specification》中式这样定义Class文件的结构:
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];
}
接下来我们一起看看这个表中各个数据项的具体含义。使用下面的测试类进行详细说明:
package com.igood;
public class TestClass {
private int member;//int类型字段
public static String HOME_URL="http://www.baidu.com";//static字符字段
public final float PI = 3.14159F;//final字段
//构造函数
public TestClass(){
}
//getMember函数
public int getMember() {
return member;
}
}
编译完成的类文件如下:
每个Class文件的头4个字节称为魔数,它的唯一作用是用于确定这个文件是否为一个能被虚拟机接受的Class文件,它的值为0xCAFEBABE。
2、minor_version和major_version
紧接着魔数的4个字节存储的是Class文件的版本号:第5、6个字节是次版本号(minor_version),第7、8字节是主版本号(major_version)。Java的版本号从45开始。
紧接着主次版本号之后的是常量池入口,由于常量池中常量的数量是不固定的,所以再常量池的入口需要放置一项u2的类型的数据,代表常量池容量计数值(constant_pool_count),这个容量计数是从1而不是从0开始的。常量池(constant_pool)存储了诸如符号常量、final常量值、基本数据类型的字面值等内容。在jVM的头文件jvm/vmcommon/h/pool.h中,有以下对常量池项类型的定义:
#define CONSTANT_Utf8 1 //UTF-8编码的Unicode字符串
#define CONSTANT_Integer 3 //int类型的字面值
#define CONSTANT_Float 4 //float类型的字面值
#define CONSTANT_Long 5 //long类型的字面值
#define CONSTANT_Double 6 //double类型的字面值
#define CONSTANT_Class 7 //对一个类或接口的符号引用
#define CONSTANT_String 8 //String类型字面值的引用
#define CONSTANT_Fieldref 9 //对一个字段的符号引用
#define CONSTANT_Methodref 10 //对一个类中方法的符号引用
#define CONSTANT_InterfaceMethodref 11 //对一个接口中方法的符号引用
#define CONSTANT_NameAndType 12 //对一个字段或方法的部分符号引用
constant_pool:表类型数据集合,即常量池中每一项常量都是一个表,共有11种结构各不相同的表结构数据。这11种表都有一个共同的特点,即均由一个u1类型的标志位开始,可以通过这个标志位来判断这个常量属于哪种常量类型,常量类型及其数据结构如下表所示:
类型 |
简介 |
项目 |
类型 |
描述 |
CONSTANT_Utf8_info |
utf-8缩略编码字符串 |
tag |
u1 |
值为1 |
length |
u2 |
utf-8缩略编码字符串占用字节数 |
||
bytes |
u1 |
长度为length的utf-8缩略编码字符串 |
||
CONSTANT_Integer_info |
整形字面量 |
tag |
u1 |
值为3 |
bytes |
u4 |
按照高位在前储存的int值 |
||
CONSTANT_Float_info |
浮点型字面量 |
tag |
u1 |
值为4 |
bytes |
u4 |
按照高位在前储存的float值 |
||
CONSTANT_Long_info |
长整型字面量 |
tag |
u1 |
值为5 |
bytes |
u8 |
按照高位在前储存的long值 |
||
CONSTANT_Double_info |
双精度浮点型字面量 |
tag |
u1 |
值为6 |
bytes |
u8 |
按照高位在前储存的double值 |
||
CONSTANT_Class_info |
类或接口的符号引用 |
tag |
u1 |
值为7 |
index |
u2 |
指向全限定名,常量项的索引 |
||
CONSTANT_String_info |
字符串类型字面量 |
tag |
u1 |
值为8 |
index |
u2 |
指向字符串字面量的索引 |
||
CONSTANT_Fieldref_info |
字段的符号引用 |
tag |
u1 |
值为9 |
index |
u2 |
指向声明字段的类或接口描述符CONSTANT_Class_info的索引项 |
||
index |
u2 |
指向字段描述符CONSTANT_NameAndType_info的索引项 |
||
CONSTANT_Methodref_info |
类中方法的符号引用 |
tag |
u1 |
值为10 |
index |
u2 |
指向声明方法的类描述符CONSTANT_Class_info的索引项 |
||
index |
u2 |
指向名称及类型描述符CONSTANT_NameAndType_info的索引项 |
||
CONSTANT_InterfaceMethodref_info |
接口中方法的符号引用 |
tag |
u1 |
值为11 |
index |
u2 |
指向声明方法的接口描述符CONSTANT_Class_info的索引项 |
||
index |
u2 |
指向名称及类型描述符CONSTANT_NameAndType_info的索引项 |
||
CONSTANT_NameAndType_info |
字段或方法的部分符号引用 |
tag |
u1 |
值为12 |
index |
u2 |
指向该字段或方法名称常量项的索引 |
||
index |
u2 |
指向该字段或方法描述符常量项的索引 |
4、access_flags(访问标志) 占用2个字节。用来表明该class文件中定义的是类还是接口,访问修饰符是否定义为public;是否定义为abstract类型。类是否是final的。
标志名称 |
标志值 |
含义 |
ACC_PUBLIC |
0x0001 |
是否为public类型 |
ACC_FINAL |
0x0010 |
是否被声明为final,只有类可设置 |
ACC_SUPER |
0x0020 |
是否允许使用invokespecial字节码指令,JDK1.2以后编译出来的类这个标志为真 |
ACC_INTERFACE |
0x0200 |
标识这是一个接口 |
ACC_ABSTRACT |
0x0400 |
是否为abstract类型,对于接口和抽象类,此标志为真,其它类为假 |
ACC_SYNTHETIC |
0x1000 |
标识别这个类并非由用户代码产生 |
ACC_ANNOTATION |
0x2000 |
标识这是一个注解 |
ACC_ENUM |
0x4000 |
标识这是一个枚举 |
根据上面的表格,TestClass类的访问标志为0x0021 = ACC_PUBLIC | ACC_SUPER = 0x0001 | 0x0020
5、this_class 占用2个字节。 它是一个对常量池的索引。指向的是常量池中存储类名符号引用的CONSTANT_Class_info常量表(见下面常量池具体结构)。比如this_class=0x0001。则表示指向常量池中的第一个常量表。通常这个表是指向当前class文件所定义的类名。
6、super_class 占用2个字节 与this_class类似,指向存放当前class文件所定义类的超类名字的索引的CONSTANT_Class_info常量表。
7、inteface_count、interfaces
interface_count是class文件所定义的类直接实现的接口或父类实现的接口的数量。占2个字节。intefaces包含了对每个接口的CONSTANT_Class_info常量表的索引。
由于interface_count为0,所以接口索引集合interfaces大小为0,即在编译后的二进制文件中不存在interfaces这项内容。
8、fields_count、fields
fields_count字段计数器,表明了类中字段的数量 。fields是不同长度的field_info表的序列。这些field_info表中并不包含超类或父接口继承而来的字段。field_info表展示了一个字段的信息,包括字段的名字,描述符和修饰符。如果该字段是final的,那么还会展示其常量值。注意,这些信息有些存放在field_info里面,有些则存放在field_info所指向的常量池中。fields:字段表集合,一组字段表类型数据的集合,字段表包括access_flags,name_index,descriptor_index,attributes_count和attributes[attributes_count]。
9、method_count、methods
与字段类似,method_count表明类中方法的数量和每个方法的常量表的索引。methods表明了不同长度的method_info表的序列。methods:方法表集合,一组方法表类型数据的集合。方法表结构和字段表结构一样包括access_flags,name_index,descriptor_index,attributes_count和attributes[attributes_count]。
标志名称 |
标志值 |
含义 |
ACC_PUBLIC |
0x0001 |
字段是否为public |
ACC_PRIVATE |
0x0002 |
字段是否为private |
ACC_PROTECTED |
0x0004 |
字段是否为protected |
ACC_STATIC |
0x0008 |
字段是否为static |
ACC_FINAL |
0x0010 |
字段是否为final |
ACC_SYNCHRONIZED |
0x0020 |
字段是否为synchronized |
ACC_BRIDGE |
0x0040 |
方法是否是由编译器产生的桥接方法 |
ACC_VARARGS |
0x0080 |
方法是否接受不定参数 |
ACC_NATIVE |
0x0100 |
字段是否为native |
ACC_ABSTRACT |
0x0400 |
字段是否为abstract |
ACC_STRICTFP |
0x0800 |
字段是否为strictfp |
ACC_SYNTHETIC |
0x1000 |
字段是否为编译器自动产生 |
10、attributes_count 和 attributes属性表
在Class文件、属性表、方法表中都可以包含自己的属性表集合,用于描述某些场景的专有信息与Class文件中其它数据项对长度、顺序、格式的严格要求不同,属性表集合不要求其中包含的属性表具有严格的顺序,并且只要属性的名称不与已有的属性名称重复,任何人实现的编译器可以向属性表中写入自己定义的属性信息。