Class类文件结构

计算机只能执行机器码,而机器码是一系列0和1的组合。直接编写机器码肯定不行,为了简化开发就有了汇编语言,由汇编语言翻译成机器码。汇编开发也复杂,就有了C语言,由C语言翻译成汇编语言。后来就由C写出了Java。在各种IDE平台上写的程序都是.java文件,经过编译器编译为.class文件,然后JVM拿到这些.class文件经过解释器生成可执行的机器码。各种不同平台的虚拟机和所有平台都统一使用的程序存储格式—字节码,是构成平台无关性的基石。.class文件就是存储字节码的,不同平台产生的Class格式是统一的。Class文件并不是Java特有的,其他语言也可以编译成Class文件,经过JVM解释后执行。

在这里插入图片描述

任何一个Class文件都对应着唯一一个类或者接口的信息,反过来却不一定,类或文件的接口可以定义在Class文件中,也可以通过类加载器直接生成。

Class文件是一组以8个字节为基础单位的二进制字节流,各个数据项目严格按照顺序紧凑的排列在Class文件中,中间没有任何分隔符。这样Class文件存储的都是程序运行的必要数据,没有空隙。因此Class文件中存储的顺序和数量,占了多少字节、含义是社么都是被严格定义的,全部不允许改变。

Class文件格式采用类似C语言结构体的伪结构来存储数据,伪结构只包含两部分:无符号数和表

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

表是由多个无符号数或者其他表作为基本数据结构的复合数据类型,所有表都习惯性的以_info结尾。Class文件本质就是一张表,主要构成在这里插入图片描述
前4个字节称为魔数,它唯一的作用是确定这个文件是否为一个能被虚拟机接受的Class文件。这个魔数是固定的值:0XCAFEBABE,“咖啡宝贝” 如果不是此值则虚拟机认为不是Class文件,不能识别。

紧跟魔数的是Class文件的版本号。次版本号和主版本号各两个字节。与Class版本对应的是JDK的版本,JDK是向下兼容的,高版本JDK可以运行以前版本的Class而不能以后版本后的Class。随着Java的发展,Class文件的格式会发生一些改变,版本标识号表示Class属于那一代版本,加入或者修改了社么。但即使Class文件的格式未作任何改动,虚拟机也拒绝执行超过其版本号的Class文件。Class主版本从45开始对应JDK1.1,JDK每升级一代主版本号+1。
在这里插入图片描述
在版本号之后的是常量池(2字节的常量个数+每个常量池表),常量池可以比喻为Class文件里的资源仓库,它是Class文件结构中与其他项目关联最多的数据,通常也是占用Class文件空间最大的数据项目之一。在常量池入口由两个字节是存放常量个数的,这个计数是从1开始而不是0,把0腾出来是因为在特定情况下表达不引用任何一个常量池,将索引值设置为0来表示。Class文件中只有常量池是从1开始计数,其他的容量计数都是从0开始。

常量池中主要存放两大类常量:字面量和符号引用。字面量比较接近Java语言层面常量概念,如文本字符串、声明为final的常量值等。而符号引用属于编译原理方面概念,主要包括:
被模块导出或者开放的包
类和接口的全限定命
字段的名称和描述符
方法的名称和描述符
方法句柄和方法类型
动态调用点和动态常量

常量池的每个常量都是一个表,表的第一位是一个u1类型的标志位,用来表示属于那种常量类型。目前常量池的表共有21种类型,标志位的范围从1—21。这里的标志是按u1的数值来的,下面的访问标志是按位数来的。

常量池之后有2个字节代表访问标志,这个标志用于识别类或者接口层次的访问信息。2个字节一共是16位,当某位为0时表示没有启动标志,置1时表示使用标志。这样做的好处是可以直接与,得出最后的标志组合,例如0x0001表示public,0x0400表示是一个接口,与结果解释public类型的接口。目前16个里面只定义了9个
在这里插入图片描述
接下来是类索引、父类索引和接口索引集合。类索引和父类索引都是u2类型,类索引用于确定这个类的全限定名,父类索引用是父类的全限定名,Java不允许多继承,所以父类索引只可能有一个,而除了Object类之外,所有的类都有父类。故除了Object类,所有Java类的父类索引都不为0。接口可以实现多个,所以是接口索引集合。先有2个字节来记录实现了多少接口,然后是每个接口的全限定名(按照代码编写时Implements字后的顺序排列)。如果一个接口也没有,那接口计数器为0,后面接口索引表不占任何字节。

然后是字段表集合、方法表集合、属性表集合。都是有2个字节才存放数量,然后跟上具体的字段表、方法表或者属性表。

字段表描述接口或者类中声明的变量,Java的字段包括类级变量以及实例变量,但是不包含在方法内部的局部变量。字段表包含访问标志、字段简单名称、字段和方法的描述符、属性表集合,访问标志就是public private static final等等,也是采用1个位表示一种含义,最后相与得最终结果。简单名称就是没有类型和参数修饰的方法和字段,描述符的用来描述字段的数据类型、方法的参数列表和返回值。字段表不会出席从父类或者接口种继承而来的字段,但是可能出现原本Java代码中不存在的字段(譬如使用内部类时)。编写java程序时字段无法重载,也就是在一个作用域内必须使用不一样的名称,但是在Class内只要字段的描述符不完全相同,字段重名就合法。

方法表和字段表几乎格式几乎相同。访问标志、名称索引、描述符索引、属性表集合。只是在访问标志和属性表集合可选项中有区别。比如volatile和transient不能修饰方法却可以修饰变量就移除,而增加了native、abstract等关键字修饰方法。而每个方法的代码都储存在Code属性里面。与字段表相类似,如果父类方法没有在子类中重新写,方法表集合就不会出现来自父类的方法信息。但是同样可能出现程序中不存在的方法,比如默认构造函数等。虽然程序没有写,但是编译器还是会自动添加到方法区集合中。

方法时可以重载的,要求必须拥有相同的方法名和不同的方法特征签名。在Java代码中,方法签名包括方法名称、参数顺序、参数类型。而在字节码中,字节码的特征签名还包括返回值以及受检异常表。

属性表集合,上面好多都包含属性表。属性表现在已经有29类。

对于属性表,结构为属性表类别(u2)、数据的长度(u4)、具体的数据。属性表类别是一个指向常量池的指针,常量的值为“Code” ""等29个中的某一个。然后是属性值的总长度。

Code属性值包含:
max_stack:操作数栈的最大深度,虚拟机执行方法是根据这个值才分配操作栈深度
max_locals:局部变量存储所需要的空间,单位是slot(虚拟机分配内存的最小单位)
code_length:字节码指令长度
code:存储字节码指令的一系列字节流
exception_table_length:显示异常处理表的长度
exception_table:异常表。此处指的是try/catch/finally所捕获的异常。

Exception属性:列举出方法可能抛出的受检异常,这里是指方法throws后所跟的异常。

LineNumberTable属性:描述源码行号和字节码行号之间对应关系。调试的时候依靠这个属性,可以选择取消,但取消后无法设置调试断点。

LocalVariableTable/LocalVariableTypeTable属性:描述栈帧中局部变量表的变量与Java源码中定义的变量之间的关系

SourceFile及SourceDebugExtension属性:SourceFile属性用于记录生成这个Class文件的源码文件名称,
SourceDebugExtension属性用于存储额外的代码调试信息

ConstantValue属性:通知虚拟机自动为静态变量赋值

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

Signature属性:记录泛型签名信息

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值