Class文件中,会用两个字节表示访问标志
比如是接口吗,是否是public类型
将其简化为了两个字节
其用法就是 0x0001|0x0020 = 0x0021
表明这个为public的一个普通类
然后是各类索引,可以细分为类索引 父类的索引 接口的索引类
类索引:表示这个类的全限定的类名
父类索引:由于Java是单继承的,所以只会有一个
接口索引集合,描述实现了什么接口,是一个接口索引的集合
排在了访问标示之后,都是u2类型的数据,都会指向一个CONSTANT_CLASS_info 类描述符变量
内部的字段表的集合
用于描述接口或者类中生命的变量,也是一个info类型
例如
public static final String INFO = “is”
只是类变量,不包括方法中的局部变量,包括的信息有,字段的作用域,包括但不限于public private protected等
类变量还是实例变量 static 是否为静态 final ,并发安全 volatile
然后是这个字段表的具体内容
在标示位后面是两个索引值
都是对常量池的引用
name_index 用于描述 全限定类名或者说是简化的简化名字
比如说一个叫addOne()的方法,简单名称就是addOne
descriptor_index 是描述符,用于描述字段的数据类型 参数类型,返回值
属性信息,表明的是字段的数据类型,参数类型,返回值
数组比较特殊,需要使用 [ 来描述 ,假设一个二维数组 String[][] 就将记录为了[[String
一个方法概述为
int addOne(int a, int[] b)那就是
(I[I[I) i
对于类中的方法
方法表和之前的字段表一致,依次包括了访问标志 名称索引 描述符索引和属性表集合
只是对应的字段不一样
那么在方法表之前的,是一个u2类型的计数器,说明其中有几个方法
然后就是方法表用于描述方法详情(业务代码除外),
顺便提一下,如果父类方法在子类中没有重写,方法表的集合中是不会显示出现的
但是可能出现像 构造器这种由编译器书写的方法
对于类中,还有一个属性表
专门用于描述某些场景下的信息,也是前面很多表的最后一项
可以被用于 Class 文件 字段表 方法表使用
其中我们主要说一下其中的code表
将Java方法体中的代码进行编译之后,最终变为字节码文件放在Code中,但不是所有的方法表都存在,抽象方法就可以没有
其常见的结构如下
attribute_name_inde指向一个常量,值恒定为 Code
attribute_length 指示了属性值的长度
max_stack 操作栈的最大深度,执行任意方法都不会超过深度,这个交由java虚拟机在运行时候,分配栈帧深度
max_locals 存储局部变量所需的存储空间,计算单位为Slot
对于32位的类型,为1个Slot,对于64位的数据类型为2个Slot
而这些分配出来的Slot,可以给各个变量使用,而且支持复用,也就是在代码执行过程中,有些变量作用域一旦被结束了,就可以分配给其他变量使用
code_length是指字节码长度
虽然其是一个u4的长度,但是一旦超过65535个字节码,也无法被编译,一般来说,JSP中如果将页面代码放进去,可能导致过长而编译失败
code为真正的代码
是Java程序中真正重要的属性,我们将其中涉及到的字节码指令放在下一节来讲
其中的执行流程可以概述为,读取到字节码区域的长度后,将以此读取后面的字节码指令,然后推送到操作栈栈顶,这也是由于Java在执行过程中选择了栈这种数据结构而决定的
code指令之后,是异常处理表
在之后是异常处理表,这是一个可选择的项,因为不是必要存在的
其的基本结构很简单
可以理解为,在start_pc到end_pc行之间如果出现了catch_type的异常,那么会转到handler_pc行去处理
这就是try-catch-finally的处理机制
然后是Exceptions属性,这个是配合上面的异常表使用的,用于列举方法中可能抛出的受检查异常,也就是throws关键字后的异常,其结构如下
这个表说明了,可能抛出number_of_exceptions个异常,并指向了可能出现异常的表(一个CONSTANT_Class_indo)
剩下还有些非必要的属性
我们一概而述
LineNumberTable属性
描述Java源代码和字节码行数之间的对应关系,并非必要属性,可以通过-g:none或者-g:lines来设置
但取消之后,抛出异常时候,不会显示出错的行号
line_number_table之中有着start_pc和line_number两个数据项
为一种key-value的映射关系
LocalVariableTable属性
用于描述栈帧中局部变量表的变量和源码中定义的变量之间关系,不是默认的信息,也可以通过-g:none或者-g:vars来设置
单取消后,别人引用了参数名称会消失,会用默认的arg0,arg1来代替
其表结构如下
local_variable_info是一个栈帧和源码中局部变量的对应关系
start_pc length表示这个变量的起始地点和偏移量,组合起来就是覆盖范围
index是所在的Slot位置
而且现在对于泛型,会使用
LocalVariableTypeTable这个表,将descriptor_index替换称为字段的特征签名
SourceFile属性
记录生成这个Class文件的源码文件名称,也是可选择的,通过-g:none或者-g:source来设置
如果不生成这个类,会在抛出异常的时候,不显示代码所属的文件名,结构如下
ConstantValue
通知虚拟机及为静态变量赋值的,
这是因为虚拟机对变量赋值的方式不同,对于非static变量,是在实例构造器<init>进行的
类变量则可以通过<clint>或者 ConstantValue来修饰变量,
现在来说,使用ConstantValue属性值修饰的,只限于基本数据类型和String
表结构为
InnnerClasses属性
内部类和宿主类之间的关系
为了描述内部类的,也就是这个类文件生成的时候查看是否有内部类,有的话为其生成内部类属性
表结构为
number_of_classes,记录存在多少个内部类
每一个都是用inner_classes_info来描述
inner_classes_info可以描述为
inner_classes_info_index 指向了内部类的符号引用
outer_class_info_index 指向了诉诸来的符号引用
name_index指的是内部类的名称,匿名内部类为0
access_flags 表示访问标志
Depreacted属性和Synthetic属性
其实就是@deprecated注解,标记为不建议使用
Synthetic表示这个方法或者字段不是由Java源码产生的,是编译器自行添加的
其表结构㘝
StackMapTABLE属性
是一个类型校验器
在过去版本的校验器是通过数据流来分析的类型推导验证器
现在啊来说编译阶段通过一系列的验证类型直接记录在Class文件中
其包括了多个映射帧,标记了多个偏移量,表示执行到该字节码时候局部变量表和操作数栈的验证类型
通过这个来进行运行时候检测
表结构为
Signature属性
为了记录泛型类型,因为Java提供的是伪泛型
在编译后,统统擦除,使用擦除法易于实现,可以节省内存空间
但是就不如像C#,那样真泛型支持的语言那样,泛型类型也是普通类型
其表结构为
这个表结构可以适用于类签名 方法类型签名 字段类型签名