JVM学习笔记(二)——Class文件结构

Class文件是Java程序跨平台的保证,正是由于有了Class文件架起源码和机器码之间的中间桥梁,JVM虚拟机才可以在各种平台上按照统一的规范标准加载Java代码。

作为“写给虚拟机看的”Java代码,Class文件结构必须设计得足够完善,同时由于Java虚拟机规范并不只针对Java,Class文件又不能引入过多细节。本篇博客我们就来介绍下Class文件的结构。

一个Class文件对应一个Java Class,所以一个Class文件记录着一个类的全部信息,JVM通过Class文件将对应的类加载入内存。

Class文件的结构主要分为以下几部分:

  • 魔数
  • 常量池
  • 访问标识
  • 类索引、父类索引、接口索引
  • 字段表集合
  • 方法表集合
  • 索引表集合

1 魔数

每个Class文件的头4个字节成为魔数(Magic Number),它的唯一作用就是确定这个文件是否能作为一个Class文件被接受。很多文件都以魔数进行类型识别,如gif、jpeg等图片文件。之所以使用魔数而不是扩展名是处于安全考虑,文件扩展名可以所以改动。Class文件的魔数是0xCAFEBABE。

紧接着魔数的4个字节存储的是Class文件的版本号,5、6字节为次版本号,7、8字节为主版本号。不同版本的虚拟机可以接受不同版本的class文件,所以虚拟机通过主次版本号判断是否可以加载目标class文件。

2 常量池

常量池可以看做是Class文件的资源仓库,也是Class文件中占用空间最大的部分。常量池主要存放两大类常量:字面量、符号引用。

字面量比较接近Java语言层面的常量,如文本字符串、生命为final的常量等。

符号引用属于编译范畴中的概念,主要包括三类常量:

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

Java语言不同于C、C++等语言在编译阶段即进行链接,相应的链接都放到了运行时阶段。所以Class文件中不可能包含各个方法、字段在内存中的布局。Java虚拟机在运行阶段加载类时,将符号引用转换成真正的内存入口地址,对应类才算可以工作。

常量池中的每一项代表一个常量,JDK目前共有14中类型的常量,而每一个常量又有自己的内部结构。类或接口符号索引是其中较为简单的一项,接下来以类索引为例做简单介绍。类符号索引对应的类型为CONSTANT_Class_info,其结构如下:

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

tag是标志位,表明类型。CONSTANT_Class_info的tag为7。name_index是一个索引值,它指向常量池中一个CONSTANT_Utf8_info类型常量,此常量代表了这个类的全限定名。

CONSTANT_Utf8_info的结构如下所示:

类型 名称 数量
u1 tag 1
u2 length 1
u1 bytes length

bytes字段的内容就是类的全限定名。

3 访问标志

常量池之后的两个字节代表访问标志(accss_flags),用于识别类或接口的层次访问信息:

标志名称 标志值 含义
ACC_PUBLIC 0x0001 是否为public
ACC_FINAL 0x0010 是否被声明为final
ACC_SUPER 0x0020 是否允许使用invokespecial字节码指令的新语义
ACC_INTERFACE 0x0200 是否为接口
ACCS_ABSTRACT 0x0400 是否为abstract类型
ACC_SYNTHETIC 0x1000 标示该类并非由用户代码产生
ACC_ANNOTATION 0x2000 标示这是一个注解
ACC_ENUM 0x4000 标示这是一个枚举

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

类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interfaces)是一组u2类型的数据集合。Class文件中的这三项决定了类的继承关系。

类索引和父类索引用两个u2类型的索引值表示,它们各自指向一个CONSTANT_Class_info类描述符常量,通过CONSTANT_Class_info类型的常量索引值可以找到定义在CONSTAN_Utf8_info类型的常量中的类全限定名。

对于接口索引集合,入口的第一项u2类型的数据为接口计数器(interfaces_count)表示索引表的容量。每个接口的同样由一个u2类型数据指向一个CONSTANT_Class_info。

5 字段表集合

字段表(field_info)用于描述接口或者类中声明的变量。字段(field)包括类级变量和实例级变量,但不包括定义在方法内部的局部变量。每个字段的结构如下图所示:

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

5.1 访问标识

标志名称 含义
ACC_PUBLIC 是否为public
ACC_PRIVATE 是否为private
ACC_PROTECTED 是否为protected
ACC_STATIC 是否为static
ACC_FINAL 是否为final
ACC_VOLATILE 是否为volatile
ACC_TRANSIENT 是否为transient
ACC_SYNTHETIC 是否为编译器自动产生
ACC_ENUM 是否为enum

字段的访问标识access_flags与类访问标识类似。

5.2 name_index

name_index标识字段的简单名称。简单名称和全限定名的区别在于:全限定名是类的全路径名,如org/fenixsoft/clazz/TestClass,只是把类全名中的"."替换成“/”而已。简单名称指的是没有类型和参数修饰的方法或者字段名称,如一个类中含有一个字段"m",则其简单名称为"m"。

5.3 descriptor_index

descriptor_index为字段或方法的描述符。描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。基本数据类型以及代表无返回值的void以及对象类型均由一个大写字符来代替:

标志字段 含义
B byte
C char
D double
F float
I int
J long
S short
Z boolean
V void
L 对象类型,如Ljava/lang/object

对于数组类型,每一个维度用一个"["来描述,比如定义一个“java.lang.String[][]”类型的二维数组,将被记录为“[[Ljava/lang/string”。

方法描述符按照先参数列表后返回值的顺序描述,参数列表按照参数顺序放在一组"()"之内。如方法int indexOf(char[] source, int sourceOffest, int sourceCount, char[] target, int targetOffest, int targetCount, int fromIndex)的描述符为"([CII[CIII)I"。

5.4 attributes_count attribute_info

在描述符之后还有数量为attributes_count的attribute_info,attribute_info描述字段的额外信息,但这些额外信息最终存放在属性表中。如“final static int m = 123;”,那就可能会存在一项名称为ConstantValue的属性,其值指向常量123。

6 方法表集合

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

方法表和字段表结合几乎一样,理解了字段表,方法表就非常简单了。

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

由于volatile和transient不能修饰方法,所以方法表的访问标识中没有了ACC_VOLATILE,ACC_TRANSIENT标识。但同时又增加了代表synchronized native strictfp abstract的ACC_SYNCHRONIZED ACC_NATIVE ACC_STRICTFP ACC_ABSTRACT。

需要说明的是,方法表集合中并不包含方法里面的代码。方法代码经过编译后存放在方法属性集合中的一个名为"Code"的属性里面。例如某方法的属性表计数器attributes_count为1,则表示方法的属性表集合有一项属性,属性索引名称为0x0009,对应常量为code,说明此属性是方法的字节码描述。

7 属性表集合

Class文件、字段表、方法表都可以有自己的属性表,Java7里面定义了21种属性。

Code属性

并非所有方法表都有Code属性,比如接口和抽象类的方法就没有。结构如下:

类型 名称 数量 含义
u2 attribute_name_index 1 属性名的索引,对Code属性而言恒为”Code”
u4 attribute_length 1 属性值长度,相当于整个属性表长度长度减6(u2+u4)
u2 max_stack 1 操作数栈深度最大值。JVM运行时根据此值分配栈桢的操作栈深度
u2 max_locals 1 局部变量表所需存储空间,单位是Slot,double和long占用2个Slot、其他基本类型1Slot,Slot空间可以重用(变量作用域问题)
u4 code_length 1 编译后的字节码长度,理论上最长2^32-1,实际上JVM规定一个方法不允许超过65535条字节码指令
u1 code code_length 代码编译后的字节码
u2 exception_table_length 1 异常表长度
exception_info exception_table exception_table_length 异常表,记录字节码在start_pc到end_pc行之间如果出现类型为catch_type或其子类的异常则跳转到handler_pc行继续处理
u2 attibutes_count 1 属性表计数器
attribute_info attributes attibutes_count 属性额外描述,比如描述变量初始化值在常量池中的索引

字节码值得注意的一个地方是,javac编译时将this关键字作为一个普通方法参数由JVM调用时自动传入。

Exceptions属性

描述方法可能抛出的受检异常。

LineNumberTable属性

描述Java远吗行号与字节码行号之间映射关系,也就是为什么抛异常的时候可以显示源码哪一行抛出的。

LocalVariableTable属性

描述栈桢中局部变量表与Java源码中变量的关系,以保证编译后的代码被其他代码调用时,IDE可以显示参数名(否则被arg0、arg1之类的变量名代替)

SourceFile属性

描述生成当前Class文件的源文件名称,也是抛异常时可以显示源文件名字的原因。但内部类不会生成这个属性。

ConstantValue属性

static关键字修饰的变量可以使用这个属性。对于Sun javac编译器,final static的变量采用ConstantValue属性初始化,其他static变量在<clinit>(类构造器)中初始化。

InnerClasses属性

记录内部类和宿主类的关联。内部类和宿主类的Class文件都会有这个属性。

Signature属性

记录泛型签名信息。Java的泛型是使用擦除式实现的伪泛型,编译后擦除泛型,这个属性为了弥补此缺陷,方便反射API可以拿到泛型类型。

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

推荐阅读更多精彩内容

  • 字节码查看工具:WinHex 前言 Java虚拟机实现语言无关性的基石就是Class文件Java虚拟机提供的语言无...
    zlcook阅读 7,124评论 4 18
  • 晚秋只道意绵绵 枯叶点点 梦里回转 他乡更比凄零雨 却孰知 茶香徐徐映星辰
    兰阁雨湘阅读 184评论 4 4
  • “人们数不清她的屋顶上有多少轮皎洁的明月, 也数不清她的墙壁之后那一千个灿烂的太阳。” ...
    心有灵汐词为梦阅读 422评论 0 4
  • Node.js 安装配置本章节我们将向大家介绍在windows上安装Node.js的方法。本安装教程以Node.j...
    pwld阅读 171评论 2 3