Java类文件结构解析

本文是《深入理解Java虚拟机》中类文件结构一章的读书笔记。另外,推荐阅读Java字节码结构解析来加深理解。

Class文件组成内容

class文件是一组以8位字节为基础的二进制流,其与Java虚拟机指令集和符号表以及若干其他辅助信息相对应。

该设计有如下优点:

  1. 平台无关性,class文件可以运行在任意平台,无需考虑各个平台机器指令集不同的问题
  2. 语言无关性,不论何种语言,只要生成的class文件格式符合JVM虚拟机规范即可

注:如果遇到8位字节以上空间的数据,则会按照高位在前的方式分割成若干个8位字节进行存储(Big-Endian,具体是指最高位字节在地址最低位、最低位字节在地址最高位的顺序来存储数据,它是SPARC、PowerPC等处理器的默认多字节顺序,而x86等处理器则是使用了相反的 Little-Endian 顺序来存储数据)

Class文件数据结构

class文件采用了类似C语言结构体的形式来存储数据,主要有以下几个特点:

  • 由无符号数和表两种数据结构组成
  • 集合,用来描述同一类型但数量不定的多个数据,格式为 容量计数器 + 数据集合
  • 没有任何分割符号(每个字节代表的含义,长度,先后顺序都不允许改变)

无符号数

定义:class文件基本的数据类型,用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。

表现形式:以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节、8个字节的无符号数。

组成:由无符号数或者其他表作为数据项构成的复合数据类型

特征:以_info 结尾

功能:用于描述有层次关系复合结构的数据

整个class文件本质上就是一张表

Class文件数据项

按照class文件中字节码的顺序来介绍数据项。

魔数

class文件的头4个字节。

功能:验证该文件是否能够被虚拟机接受

扩展名可以被修改

主次版本号

魔数后4个字节,第5个和第6个字节是次版本号(Minor Version),第7个和第8个字节是主版本号(Major Version)。

Java版本号从45开始,每个大版本发布版本号 +1

虚拟机拒绝超过其版本号的Class文件

常量池

可以说常量池是class文件的资源仓库,主要存放两大类常量,字面量和符号引用。

结构:容量计数器(u2类型) + 常量

容量计数从1开始,目的是满足某些常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池”的含义。

字面量(Literal): 类似Java中的常量,如文本字符串,声明为final的常量值等。

符号引用(Symbolic References):包括类和接口的全限定名(Full Qualified Name),字段的名称和描述符(Descriptor),方法的名称和描述符这三类常量。

常量池中的表

每一项常量都是一个表。到JDK1.7为止,共有14种常量表类型,表结构见文章末尾(常量池中的14种常量项的结构总表)。

所有常量表开始第一位为u1类型的标志位,标识常量类型。

以下是常量池的项目类型表

类型 标志 描述
CONSTANT_Utf8_info 1 UTF-8编码的字符串
CONSTANT_Integer_info 3 整形字面量
CONSTANT_Float_info 4 浮点型字面量
CONSTANT_Long_info 5 长整形字面量
CONSTANT_Double_info 6 双精度浮点型字面量
CONSTANT_Class_info 7 类或接口的符号引用
CONSTANT_String_info 8 字符串类型字面量
CONSTANT_Fieldref_info 9 字段的符号引用
CONSTANT_Methodref_info 10 类中方法的符号引用
CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
CONSTANT_NameAndType_info 12 字段或方法的部分符号引用
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_MethodType_info 16 表示方法类型
CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点

每一种常量类型有着自己的结构,下面以CONSTANT_Class_info类型为例,它的结构如下表:

类型 名称 数量
u1 tag 1
u2 name_index 1

上表中的tag用来区分常量类型,name_index是一个索引值,它指向常量池中一个CONSTANT_Utf8_info类型常量,代表了这个类(或者接口)的权限定名。

访问标志

常量池之后两个字节标识类的访问标志,用于识别一些类或者接口层次的访问信息。

具体标志位及标志含义见下表

标志名称 标志值 含义
ACC_PUBLIC 0x0001 是否为public类型
ACC_FINAL 0x0010 是否被声明为final,只有类可设置
ACC_SUPER 0x0020 是否允许使用invokespecial字节码指令的新语言,invokespecial指令的语意在JDK1.0.2发生过改变,为了区别这条指令使用哪种语意,JDK1.0.2之后编译出来的类的这个标志都必须为真
ACC_INTERFACE 0x0200 标识这是一个接口
ACC_ABSTRACT 0x0400 是否为abstract类型,对于接口或者抽象类来说,此标志值为真,其他类值为假
ACC_SYNTHETIC 0x1000 标识这个类并非由用户代码产生的
ACC_ANNOTATION 0x2000 标识这是一个注解
ACC_ENUM 0x4000 标识这是一个枚举

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

类索引、父类索引

类索引、父类索引都是一个u2类型的数据。它们会对应到常量池中的类描述符常量,通过常量中的索引值就可以找到类的全限定名字符串。

接口索引

接口索引集合是一组u2类型的数据的集合。第一项u2类型的数据为接口计数器,表示接口索引表的容量,如果该类没有实现任何接口,该计算器值为0。

Class文件中由这三项数据来确定类的继承关系。

字段表集合

字段表用于描述接口或者类中声明的变量,包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。

字段表使用标志位表示修饰符,引用常量池中的常量描述字段名及字段数据类型。

字段表集合中不会列出从超类或者父接口中继承而来的字段,但可能列出原本Java代码之中不存在的字段,譬如,在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。

Java语言中字段是无法重载的,必须使用不同的名称,但是对于字节码来说,字段可以重名,只要字段的描述符不一致

字段结构表

类型 名称 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

access_flags表示字段修饰符,与类的access_flags类似,并且都是一个u2的数据类型。

标志位及含义见下表

标志名称 标志值 含义
ACC_PUBLIC 0x0001 字段是否public
ACC_PRIVATE 0x0002 字段是否private
ACC_PROTECTED 0x0004 字段是否protected
ACC_STATIC 0x0008 字段是否static
ACC_FINAL 0x0010 字段是否final
ACC_VOLATILE 0x0040 字段是否volatile
ACC_TRANSIENT 0x0080 字段是否transient
ACC_SYNTHETIC 0x1000 字段是否由编译器自动产生的
ACC_ENUM 0x4000 字段是否enum

name_index和descriptor_index都是对常量池的引用。

name_index代表字段的简单名称。

descriptor_index代表字段和方法的描述符。

全限定名

将类全名中的“.”替换成“/”,并在最后添加一个“;”,表示全限定名结束

简单名称

没有类型和参数的方法或者字段名称

描述符

描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值

描述符规则

  • 基本数据类型(byte、char、double、float、int、long、short、boolean)以及void都用一个大写字符来表示

  • 对象类型用字符L加对象的全限定名来表示

  • 数组类型,每一纬度使用一个前置的“[”字符来描述,如定义为"java.lang.String[][]",将被表示为"[[Ljava/lang/String",一个整形数组"int[]"将被表示为"[I"

  • 描述方法时,参数列表在前,返回值在后,且参数列表需要按顺序放在一组小括号之内

方法表集合

Class文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式,只是在访问标志和属性表集合的可选项中有所区别。

方法结构见下表

类型 名称 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

方法表的访问标志中没有ACC_VOLATILE 和 ACC_TRANSIENT 标志,增加了 ACC_SYNCHRONIZED、ACC_NATIVE、ACC_STRICTFP、ACC_ABSTRACT 标志。

方法访问标志

标志名称 标志值 含义
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 方法是否是由编译器自动产生的

如果父类方法在子类中没有被重写,方法表集合中就不会出现来自父类的方法信息,但有可能出现由编译器自动添加的方法,如类构造器<clinit>方法和实例构造器<init>方法

属性表集合

Class文件、字段表、方法表都可以有自己的属性表集合,用于描述某些场景的专有信息。属性表集合的限制更宽松一些,不要求各个属性表具有严格顺序,并且只要不与已有属性名重复即可。

Code属性

用来存储Java程序方法体中的代码经过编译处理后生成的字节码指令。每个指令是一个u1类型的单字节,共可以表达256条指令。

类型 名称 数量 含义
u2 attribute_name_index 1 指向常量池中一个CONSTANT_Utf8_info类型的常量,来表示属性名称
u4 attribute_length 1 属性值长度
u2 max_stack 1 表示操作栈深度的最大值
u2 max_locals 1 表示局部变量表所需的存储空间
u4 code_length 1 表示代码字节码长度
u1 code code_length 用来存储字节码指令的一系列字节流
u2 exception_table_length 1 异常表长度
exception_info exception_table exception_table_length Java代码的一部分,用来实现Java异常及finally处理机制(而不是简单的跳转命令)
u2 attributes_count 1 Code属性总数
attribute_info attributes attributes_count Code属性

code_length类型为u4,理论上最大可以达到2^32-1,但虚拟机规定一个方法不能超过65535条字节码指令,否则Javac编译器会拒绝编译。

Slot是虚拟机为局部变量分配内存所只用的最小单位,Javac编译器会根据变量的作用域来分配Slot给各个变量使用。

Exception属性

用于列举方法中可能抛出的受查异常(throws 关键字后面列举的异常)。

LineNumberTable属性

用于描述Java源代码行号与字节码行号之间的对应关系。

可以在Javac中分别使用-g:none或-g:lines选项来取消或要求生成这项信息,如果选择不生成,在程序运行抛出异常时,堆栈中将不会显示出错的行号,在调试程序时也无法按照源码行设置断点

LocalVariableTable属性

用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系。

可以在Javac中分别使用-g:none或-g:vars选项来取消或要求生成这项信息,如果没有生成该属性,对程序运行没有影响,只是对代码编写带来较大不便,而且在调试期间无法根据参数名称从上下文中获得参数值

SourceFile属性

该属性是一个定长属性,用于记录生成这个Class文件的源码文件名称。

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 sourcefile_index 1

可以使用Javac的-g:none或-g:source选项来关闭或要求生成这项信息,如果不生成这项属性,当抛出异常时堆栈中将不会显示出错误代码所属的文件名

ConstantValue属性

该属性为一个定长属性,用来通知虚拟机自动为静态变量赋值。

InnnerClass属性

用于记录内部类与宿主类之间的关联。

Deprecated及Synthetic属性

两者为标志类型的布尔属性,只存在是和否的区别,没有值的概念。

Deprecated属性表示某个类、字段或方法不在被推荐使用

Synthetic属性代表此字段或者方法并不是由Java源码直接产生的,而是由编译器自行添加的,比如Bridge Method

StackMapTable属性

Code属性中最多只能有一个StackMapTable属性,否则将抛出ClassFormatError异常

Signature属性

该属性是在JDK1.5发布后添加到Class文件规范的一个可选定长属性,可以出现在类、属性表和方法表的属性表中。

BootstrapMethods属性

该属性是在JDK1.7发布后增加到Class文件规范之中的一个复杂的变长属性,用于保存invokedynamic指令引用的引导方法限定符。

常量池中的14种常量项的结构总表

常量 项目 类型 描述
CONSTANT_Utf8_info 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的索引项
CONSTANT_Methodref_info tag u1 值为10
index u2 指向声明方法的类描述符CONSTANT_Class_info的索引项
index u2 指向名称及类型描述符CONSTANT_NameAndType的索引项
CONSTANT_Interface_Methodref_info tag u1 值为11
index u2 指向声明方法的接口描述符CONSTANT_Class_info的索引项
index u2 指向名称及类描述符CONSTANT_NameAndType的索引项
CONSTANT_NameAndType_info tag u1 值为12
index u2 指向该字段或方法名称常量的索引
index u2 指向该字段或方法描述符常量项的索引
CONSTANT_MethodHandle_info tag u1 值为15
reference_kind u1 值必须在1-9之间(包括1和9),它决定了方法句柄的类型。方法句柄类型的值表示方法句柄的字节码行为
reference_index u2 值必须是对常量池的有效索引
CONSTANT_MethodType_info tag u1 值为16
descriptor_index u2 值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示方法的描述符
CONSTANT_InvokeDynamic_info tag u1 值为18
bootstrap_method_attr_index u2 值必须是对当前Class文件中引导方法表的bootstrap_methods[]数组的有效索引
name_and_type_index u2 值必须是对当前常量池的有效索引,常量池在该索引出的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符

虚拟机规范预定义的属性

属性名称 使用位置 含义
Code 方法表 Java代码编译成的字节码指令
ConstantValue 字段表 final关键字定义的常量值
Deprecated 类、方法表、字段表 被声明为deprecated的方法和字段
Exceptions 方法表 方法抛出的异常
EnclosingMethod 类文件 仅当一个类为局部类或者匿名类时才能拥有这个属性,这个属性用于标识这个类所在的外围方法
InnerClasses 类文件 内部类列表
LineNumberTable Code属性 Java源码的行号与字节码指令的对应关系
LocalVariableTable Code属性 方法的局部变量描述
StackMapTable Code属性 JDK1.6中新增的属性,供新的类型检查验证器(Type Checker)检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配
Signature 类、方法表、字段表 JDK1.5中新增的属性,这个属性用于支持泛型情况下的方法签名,在Java语言中,任何类、接口,初始化方法或成员的泛型签名如果包含了类型变量(Type Variables)或参数化类型(Parameterized Type),则Signature 属性会为它记录泛型签名信息。由于Java的泛型采用擦除法实现,在为了避免类型信息被擦除后导致签名混乱,需要这个属性记录泛型中的相关信息
SourceFile 类文件 记录源文件名称
SourceDebugExtension 类文件 JDK1.6中新增的属性,SourceDebugExtension属性用于存储额外的调试信息。譬如在进行JSP文件调试时,无法通过Java堆栈来定位到JSP文件的行号,JSR-45规范为这些非Java语言编写,却需要编译成字节码并运行在Java虚拟机中的程序提供了一个进行调试的标准机制,使用SourceDebugExtension属性就可以用于存储这个标准所新加入的调试信息
Synthetic 类、方法表、字段表 标识方法或字段为编译器自动生成的
LocalvariableTypeTable JDK1.5中新增的属性,它使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加
RuntimeVisibleAnnotations 类、方法表、字段表 JDK1.5中新增的属性,为动态注解提供支持。RuntimeVisibleAnnotations属性用于指明哪些注解是运行时(实际上运行时就是进行反射调用)可见的
RuntimeInvisibleAnnotations 类、方法表、字段表 JDK1.5中新增的属性,作用与RuntimeVisibleAnnotations属性作用刚好相反,用于指明那些注解是运行时不可见的
RuntimeVisibeParameterAnnotations 方法表 JDK1.5中新增的属性,作用与RuntimeVisibleAnnotations属性类似,只不过作用对象为方法参数
RuntimeInvisibeParameterAnnotations 方法表 JDK1.5中新增的属性,作用与RuntimeInvisibleAnnotations属性类似,只不过作用对象为方法参数
AnnotationDefault 方法表 JDK1.5中新增的属性,用于记录注解类元素的默认值
BootstrapMethods 类文件 JDK1.7中新增的属性,用于保存invokedynamic指令引用的引导方法限定符

简书Markdown对表格的支持不是很好,点击阅读个人站点

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,849评论 6 13
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 1.面对冲突,我们需要神器帮助我们宣泄情绪,梳理思路,提升解决问题的智慧 人生无常,不如意的事情十有八九....
    悦亮和悦享阅读 831评论 1 1
  • 我开始原谅, 那个还在稀薄的空气中恋恋不舍的自己, 毕竟, 在一个密封的国度里, 空守那样的光景是那么的孤寂。 头...
    wu枫阅读 105评论 0 0