Class文件结构

本文详细探讨了Java Class文件的结构,包括魔数、版本号、常量池、访问标志、类索引、字段表集合、方法表集合以及属性表集合等关键组成部分。通过对Class文件的解读,揭示了Java平台无关性和跨平台执行的基础。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.引言

  • 以前编写程序都是编译成二进制本地机器码,然而现在选择了新的文件格式----------Class文件。
  • 以前总以为程序的“一次编写,到处运行”指的是java程序,现在才发现指的是所有可以编译存储为Class文件的编程语言,虚拟机可以载入和执行同一种(平台无关)的字节码。
  • 以前只知道java的跨平台是虚拟机实现的,现在底层实际是虚拟机先将程序编译成Class文件,然后在不同的平台(所谓的平台指的是不同的操作系统,不同的CPU有不一样的机器指令集)上转换成机器码。

2 Class类文件结构

Class文件以8位字节为基础的二进制流,包含无符号数和表两种数据结构。

无符号数属于基本的数据结构,以u1,u2,u4,u8分别代表1个字节,2个字节,4个字节和8个字节的无符号数,无符号数可以用来描述数字,索引引用,数量值或者按照UTF-8编码构成字符串值。

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

Class文件格式
类型名称数量
u4magic(魔数)1
u2minor_version1
u2major_version1
u2constant_pool_count1
cp_infoconstant_poolconstant_pool_count-1
u2access_flags1
u2this_class1
u2super_class1
u2interface_count1
u2interfaceinterface_count
u2fields_count1
field_infofieldsfields_count
u2methods_count1
method_infomethodsmethods_count
u2attribute_count1
attribute_infoattributeattributes_count

我们用简单的java代码待观察Class文件格式。


public class ClassForm {
	private int m;
	public static void main(String[] args) {
		
	}
	public int test()	{
		return m+1;
	}
	

}

然后用WinHex打开这个Class文件:

 

2.1魔数

·                 每个Class文件的头四个字节是魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。魔数值为:CAFEBABE。

2.2Class文件的版本(minor_version & major_version)

     第5个字节和第六个字节代表次版本号,第7个字节和第8个字节代表主版本号。如图所示次版本号是0x0000,而主版本号是0x0032,也就是十进制的52,代表是jdk1.8.JDK1.1是45,依次累加

2.3常量池(constant_pool)

                1.常量池是资源仓库,占用Class文件最大的数据项之一。第9个字节和第10个字节是指常量池里面的常量个数,001A表示26,但是0不表示常量,他在必要的时候需要表达“不引用任何一个常量池项目”的含义,所有只有25个常量。

               2.常量池中每一个常量都是一张表,这些表类型共有14中(JDK1.7),按顺序编号,便于标识。大家如果想了解具体的类型可以自行搜索。每张表类型都有它自己独特的表结构,他们的相同点就是表的第1位是一个u1类型(一个字节)的标志位(tag),代表当前这个常量属于哪种常量类型。

              3.常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References),字面量类似于Java语言中常量概念,如文本字符串,声明为final的常量值等。而符号引用则包括了下面三类常量:

  •  类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符

接下来的字节代表的就是每个常量对应表数据结构的内容。比如第11和第12位为0x07表示第一个常量的类型属于第7种:CONSTANT_Class_Info(大家可以去搜索)。该表的结构如下:

类型名称数量
u1tag1
u2name_index1

所以接下来的两个字节就代表name_index,这里是0x0002表示指向常量池中的第二个常量。我们继续往下看下一个常量:接下来一个常量的tag是0x01表示第一种常量类型:CONTSANT_Utf8_info,它的结构如下所示:

类型名称数量
u1tag1
u2length1
u1byteslength

length的值说明这个UTF-8编码的字符串是多少字节,这里是0x0009就是指9个字节,bytes就表示接下来的9个字节就表示这个字符串,他所表示的就是ClassForm这个类名(如果有包名还要加上包名)。

2.4访问标志(access_flags)

在常量池访问结束后,紧接着的两个字节代表访问标志,这个标志用于一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等。具体的标志位如下:

标志名称标志位含义
ACC_PUBLIC0X0001public
ACC_FINAL0X0010final
ACC_SUPER0X0020不是很懂,但是必须为真
ACC_INTERFACE0X0020接口
ACC_ABSTARCT0X0400抽象
ACC_SYNTHETIC0X1000并非用户代码生成的
ACC_ANNOTATION0X2000注解
ACC_ENUM0X4000枚举

标志位共2个字节,16个标志位,当前只定义其中八个,没有使用到的标志位要求一律为0.在我们写的这段代码中,只有ACC_PUBLIC和ACC_SUPER为真,所以这个标志位应为0x0021.验证如下。

2.5类索引,父类索引与接口索引(this_class, super_class, interfaces)

           类索引用于确认这个类的全限定名,父类索引用于确认这个类的父类全限定名,由于java不允许多继承,所以父类索引只有一个,因为所有类都继承于java.lang.Object,所以除了Object之外,所有的java类都有父类索引。实现的接口按照从左到右的顺序排列在接口索引集合中。它们固定用CONSTANT_Class_info常量结构,通过CONSTANT_Class_Info常量中的索引值找到定义在CONSTANT_Utf8_info中的全限定名字符串。

首先是类索引:这里是0x0001表示指向常量1,而常量1我们上面已经分析过了,常量1指向常量2,常量2表示ClassForm这个字符串。

再而是父类索引:这里是0x0003代表指向常量3,而常量3我们可以分析得出是指向常量4,而常量4代表java.lang.Object。

最后是接口索引:这里为0x0000代表没有实现接口。

2.6字段表集合(field_info)

              字段表用于描述接口或者类中声明的变量。字段包括类级变量(static)以及实例级变量,但不包括在方法内部声明的局部变量。一个字段包含的信息有很多:如作用域public,private,protected,final,static,volatile,transient,字段基本类型,名称。

字段表结构
类型名称数量
u2access_flags1
u2name_index1
u2descriptor_index1
u2attribute_count1
attribute_infoattributesattribute_count

     access_flags:表示字段修饰符

字段访问标志
标志名称标志位含义
ACC_PUBLIC0X0001字段是否为public
ACC_PRIVATE0X0002字段是否为private
ACC_PROTECTED0X0004字段是否为protected
ACC_STATIC0X0008字段是否为static
ACC_FINAL0X0010字段是否为final
ACC_VOLATILE0X0040字段是否为volatile
ACC_TRANSIENT0X0080字段是否为transient
ACC_SYNTHETIC0X1000字段是否为编译器自动产生
ACC_ENUM0X4000字段是否为enum

      name_index:字段的简单名称

      descriptor_index:字段或方法的描述符

标识字符含义标识字符含义
B基本类型为byteJ基本类型为long
C基本类型为charS基本类型为short
D基本类型为doubleZ基本类型为boolean
F基本类型为floatVvoid
I基本类型为intL对象类型,如Ljava/lang/Object

     举例:一个整型数组“int[]”将被记录为“[I”.一个String[][]二维数组将被记录为:”[[Ljava/lang/String;“。如果用来描述方法,按照参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号”()“内。如方法 void  main(String[] args) 描述为 ([Ljava/lang/String;)V(注意有个分号,全限定名后面都有一个分号)。
 

attributes_count用来需要额外描述的信息量,比如初始化等。

attributes表就是用来描述额外的信息。

我们可以进行验证:接下来的字节表示的是字段个数fields_count:这里是0x0001,表示只有一个字段。

接下来就是字段表里的第一个access_flags,文件里是0x0002表示private。

第二个是name_index:文件里为0x0005,表示指向第5个常量,而第5个常量表示m。

第三个是descriptor_index:文件里是0x0006,表示指向常量池第六个常量,第六个常量表示”I“,表示 基本类型为int。

后面的attributes_count为0x0000

综上所述,我们可以推断出代码为private int m。

2.7方法表集合(methods)

            方法表和字段表几乎采用了相同的存储格式:

             

方法表结构
类型名称数量
u2access_flags1
u2name_index1
u2descriptor_index1
u2attribute_count1
attribute_infoattributesattribute_count

access_flags由于有的修饰符不能修饰方法,所以有点变动

标志名称标志位含义
ACC_PUBLIC0X0001字段是否为public
ACC_PRIVATE0X0002字段是否为private
ACC_PROTECTED0X0004字段是否为protected
ACC_STATIC0X0008字段是否为static
ACC_FINAL0X0010字段是否为final
ACC_SYNCHRONIZED0X0020方法是否是synchronized
ACC_BRIDGE0X0040字段是否是由编译器产生的桥接方法
ACC_VARARGS0X0080方法是否接受不定参数
ACC_NATIVE0X0100方法是否为native
ACC_ABSTRACT0X0400方法是否为抽象
ACC_STRICTFP0X0800方法是否为strictfp
ACC_SYNTHETIC0X1000方法是否是由编译器自动产生

 我们在对ClASS文件进行验证:

首先的两个字节是方法数methods_count:文件里为0x0003,表示有三个方法。

之后是access_flags:文件里是0x0001,表示第一个方法是public

之后是name_index:文件里是0x0007,指向常量池中的第7个常量,第七个常量池中为 <init>(编译器自动添加)

之后是descripator_index: 文件里是0x0008,指向常量池里的第八个常量,第八个常量为 ()V(上面已经对这个符号进行了描述)

之后是attribute_count:文件里是0x0001,就表示此方法属性表集合有一项属性。

最后是attributes:文件里是0x0009,表示指向常量池中的第9个常量,第9个常量为Code,接下来会讲解Code属性。

 

2.8属性表集合

              在Class文件里,字段表,方法表都可以携带自己的属性表集合。在虚拟机规范中预定义了很多种属性,每个属性都有对用的使用位置和含义。每个属性都是用CONSTANT_Utf8_info类型常量来表示.这里就不一一介绍了这里就介绍方法表里面的Code属性。

属性表结构
类型名称数量
u2attribute_name_index1
u4attribute_length1
u1infoattribute_length

     备注:用户可以自定义info的内容,只需要标明attribute_length长度即可。

Code属性表的结构
类型名称数量
u2attribute_name_length1
u2attribute_length1
u2max_stack1
u2max_locals1
u2code_length1
u2codecode_length
u2exception_table_length1
u2exception_tableexception_table_length
u2attribute_count1
u2attributeattributes_count

max_stack:代表栈帧里操作数栈的深度的最大值。

max_locals代表局部变量所需的存储为slot空间,单位为slot。

code_length和code用来存储Java源程序编译后生成的字节码指令,code_length代表字节码长度。

Code属性是Class文件中最重要的属性,要掌握需要事件,本菜鸟还在学习中。。。。。

 

3.总结

Class文件格式所具备的平台中立(不依赖硬件及操作系统),紧凑,稳定和可扩展的特点,是java技术体系实现平台无关,语言无关两项特性的重要支柱。同时Class文件是对java虚拟机执行引擎的数据入口,接下来我们需要关注的是字节流在虚拟机执行引擎中是如何被解释执行的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值