我们知道,Java文件编译后会产生一个字节码文件(.class文件),本文介绍字节码文件的文件结构相关内容。

前言

Java诞生之初就宣称的“一次编译,处处运行”的性质一直是Java的一大特点,而Java实现这个特点的方式是将.java文件编译成.class文件,通过JVM屏蔽系统差异实现的。事实上,不仅是Java,其他的比如Kotlin、Groovy等语言也都可以通过编译成字节码文件运行在Java虚拟机上,而Java虚拟机也并不关心被编译成字节码文件之前是什么语言。




java怎么改变文件编码 java修改文件类型_java怎么改变文件编码


类文件结构

按照Java虚拟机规范,class文件结构一般是向后兼容的,新版本的Java虚拟机规范一般只是在旧版基础上进行扩充,而不对之前内容修改,因此类文件结构从第一版开始定义的一些细节几乎没有变化。我们先来看一下class文件格式。


ClassFile {
    u4             magic; //Class 文件的标志:魔数
    u2             minor_version;//Class 的次版本号
    u2             major_version;//Class 的主版本号
    u2             constant_pool_count;//常量池的数量
    cp_info        constant_pool[constant_pool_count-1];//常量池
    u2             access_flags;//Class 的访问标志
    u2             this_class;//当前类
    u2             super_class;//父类
    u2             interfaces_count;//接口
    u2             interfaces[interfaces_count];//一个类可以实现多个接口
    u2             fields_count;//Class 文件的字段属性
    field_info     fields[fields_count];//一个类会可以有个字段
    u2             methods_count;//Class 文件的方法数量
    method_info    methods[methods_count];//一个类可以有个多个方法
    u2             attributes_count;//此类的属性表中的属性数
    attribute_info attributes[attributes_count];//属性表集合
}


上面的u2u4分别代表两个字节和四个字节的无符号数,_info结尾表示是一个表结构,表结构可以理解为一个类,用于表示复合数据结构的数据。


java怎么改变文件编码 java修改文件类型_修改class文件_02


上图是class文件字节码结构组织示意图,结构非常清楚。接下来为大家依次介绍class文件结构的各个部分。

魔数


u4             magic; //Class 文件的标志:魔数


字节码文件前4个字节被称为魔数(magic number),作用是让JVM能够识别这是一个字节码文件。其实不仅是字节码文件,一些图片的格式也存在魔数。字节码文件的魔数比较可爱,它的值为0xcafebabe(咖啡宝贝)。

Class文件版本


u2             minor_version;//Class 的次版本号
    u2             major_version;//Class 的主版本号


跟在魔数后面的四个字节表示Class文件的版本号,前两个字节是次版本号,后两个字节是主版本号。高版本的JDK可以支持低版本的Class文件,而低版本的JDK无法支持高版本为Class文件。

常量池


u2             constant_pool_count;//常量池的数量
    cp_info        constant_pool[constant_pool_count-1];//常量池


constant_pool_count被用来表示常量池的常量数,这里的常量池是从1开始索引的,因为第0项是用于某些指向常量池的索引值的数据在特定情况下表示“不引用任何一个常量池项目”的含义。

常量池中存放两种类型的常量:字面量和符号引用。前者很接近Java语言对于常量的定义(字符串、被final修饰的常量等),后者则倾向于编译原理方面,主要包括:

  • 被模块导出或者开放的包(Package)
  • 类和接口的全限定名(Fully Qualified Name)
  • 字段的名称和描述符(Descriptor)
  • 方法的名称和描述符
  • 方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)
  • 动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-ComputedConstant)

常量池的每一个常量都是一个表,表结构起始的第一位是个u1类型的标志位(tag),代表着当前常量属于哪种常量类型。常见常量类型所代表的具体含义如下表所示。

类型标志(tag)描述CONSTANT_utf8_info1UTF-8编码的字符串CONSTANT_Integer_info3整形字面量CONSTANT_Float_info4浮点型字面量CONSTANT_Long_info5长整型字面量CONSTANT_Double_info6双精度浮点型字面量CONSTANT_Class_info7类或接口的符号引用CONSTANT_String_info8字符串类型字面量CONSTANT_Fieldref_info9字段的符号引用CONSTANT_Methodref_info10类中方法的符号引用CONSTANT_InterfaceMethodref_info11接口中方法的符号引用CONSTANT_NameAndType_info12字段或方法的符号引用CONSTANT_MothodType_info16标志方法类型CONSTANT_MethodHandle_info15表示方法句柄CONSTANT_InvokeDynamic_info18表示一个动态方法调用点

访问标志


u2             access_flags;//Class 的访问标志


访问标志的部分比较简单,主要用来标记类或接口的访问信息,是类还是接口,是否是public类型,是否定义为final,是否是abstract类型等。


java怎么改变文件编码 java修改文件类型_Java_03


定义一个普通的用public修饰的Java类,并且用JDK1.2以后的编译器编译,那么除了ACC_PUBLICACC_SUPER为真,其它全为假。

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


u2             this_class;//当前类
    u2             super_class;//父类
    u2             interfaces_count;//接口
    u2             interfaces[interfaces_count];//一个类可以实现多个接口


类索引和父类索引分别用来确定当前类和父类的全限定名,所谓全限定名就是类的路径全名,例如String类的全限定名是java/lang/String。由于除了Object类以外的所有类都有父类,所以只有Object类的父类索引为0。接口索引集合定义了类实现了哪些接口。

字段表集合


u2             fields_count;//Class 文件的字段属性
    field_info     fields[fields_count];//一个类会可以有个字段


字段表用来描述类/接口中的成员变量,不是描述方法中的局部变量。下面是field_info的结构:


java怎么改变文件编码 java修改文件类型_修改class文件_04


access_flag是用于确定字段的访问修饰符、是否静态变量、是否可变(final)、并发可见性(volatile修饰符,是否强制从主内存读写)、可否被序列化(transient修饰符)、字段数据类型(基本类型、对象、数组)、字段名称。下面是access_flag的取值:


java怎么改变文件编码 java修改文件类型_修改class文件_05


name_index是对常量池的引用,用于描述字段的名称。

descriptor_index也是对常量池的引用,是字段和方法的描述符。

attributes_countattributes[attributes_count]是用于存放字段的一些属性。

方法表集合


u2             methods_count;//Class 文件的方法数量
    method_info    methods[methods_count];//一个类可以有个多个方法


方法表用来描述类中的方法,method_info结构和field_info几乎一样,这里就不过多介绍了。


java怎么改变文件编码 java修改文件类型_Java_06


下面是方法表的access_flag取值:


java怎么改变文件编码 java修改文件类型_修改class文件_05


属性表集合


u2             attributes_count;//此类的属性表中的属性数
    attribute_info attributes[attributes_count];//属性表集合


属性表(attribute_info)在前面的讲解之中已经出现过数次,Class文件、字段表、方法表都可以携带自己的属性表集合,以描述某些场景专有的信息。

与Class文件中其他的数据项目要求严格的顺序、长度和内容不同,属性表集合的限制稍微宽松一些,不再要求各个属性表具有严格顺序,并且《Java虚拟机规范》允许只要不与已有属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java虚拟机运行时会忽略掉它不认识的属性。