类文件结构
class文件是一组以8字节为基础单位的二进制流,各个项目严格按照顺序紧凑地排列
- 魔数(0xCAFEBABE)与class版本:前四个字节为魔数,表示了这是个class文件,紧接着四个字节为class文件版本号,虚拟机会拒绝执行超过其版本号的文件
- 常量池:class文件之中的资源仓库。占用class文件空间最大的项目之一,常量的数量不固定,需要有个计数值。计数值从1开始,空出一个0为满足某些数据有时需要表达“不引用任何一个常量池项目”。常量池包括以下两大类常量
- 字面量:接近于java语言层面的常量概念。
- 符号引用:类和接口的全限定名(完整的包名,用"/"代替".",以";"结尾);字段的名称和描述符;方法的名称和描述符;
- 什么是描述符:描述字段的数据类型,方法的参数列表和返回值。基本类型以及void用一个大写字母表示(byte->B);对象类型用字符L加上对象的全限定名表示;数组类型,每一个维度都意味着在左边多加一个"["(String[][] -> [[Ljava/lang/String;int[] -> [I)。方法的描述符为先参数列表后参数符(int indexOf(char[] c,int i) -> ([CI)I)。
- 访问标志:紧接着常量池的两个字节,描述当前这个类的访问信息,比如是类还是接口,是否为public等。
- 类索引,父类索引,接口索引集合:用于确定这个类的继承关系。前两个长度分别为两个字节,确定这个类以及父类的的全限定名。接口索引集合按照implements的语序记录各接口。
- 字段表集合:描述接口或者类中声明的变量。描述了变量的作用域、可变性、字段名称等。实际记录的是对于常量池的引用。注意:字段表集合不会列出从超累或者父接口中继承而来的字段,但可能列出本来java代码中不存在的字段,比如内部类可能添加存在于外部类的字段;在java语言中字段必须不重名,但是在字节码中,只要描述符不同,同名的变量是可以共存的。
- 方法表集合:与字段表集合类似,同样有访问标志、名称所有、描述符索引、属性表集合。区别在于volitile和transient关键字不能修饰方法,所以不会用到这两个。如果没有覆盖父类的方法,将不会有先关的信息,有可能出现编译器自动添加的方法比如类构造器<clinit>。java语言中要重载一个方法,必须有一个与原方法不同的特征签名,即参数集合不包括返回值。但在字节码中,特征签名包括返回值,所以参数与名字完全相同的方法,只要返回值不同,是可以共存于class文件的。
- 属性表集合:用于描述某些场景专有的信息。要求比较宽松,即使插入了一些自定义属性,虚拟机也会忽略不认识的属性。
- Code属性:方法体中的代码经编译后,最终变为字节码指令存储于此。接口和抽象类的方法就不存在code属性。
- Exceptions属性:与Code属性平级,列举方法可能抛出的异常。
- LineNumberTable属性:描述java源码行号与字节码行号的对应关系。如果没有这个,在抛出错误时是无法知晓代码中哪一行出错的。
- LocalVariableTable属性:栈帧(java虚拟机栈)中局部变量表和java源码定义的变量名之间的关系。如果没有这个,调用方法者将无法知晓参数的名称,ide会自动填入arg0,arg1之类的占位符。
- ConstantValue属性:通知虚拟机自动为静态变量赋值。
- InnerClasses属性:记录内部类与宿主类之间的关联。内有记录相关类的计数值number_of_classes、外部类在常量池的索引、内部类在常量池的索引(允许有多个)、内部类的名称索引(如果是匿名内部类则指向0),内部类的访问标志。
- Deprecated和Synthetic属性:前者标记某个类、字段或方法已经被作者不推荐使用 (体现在IDE中就是将此划掉->a.test());后者表示此字段或方法不是由java源码产生的,而是编译器自己添加的。
- StackMapTable属性:一个用于优化类型推导验证器的属性,它的存在意味着验证器不用再逐个验证字节流的合法性,相当于在编译时提前做了验证。它的存在大大提升了字节码验证的性能。
- Signature属性:用于标记泛型类型。因为泛型在编译时做了类型擦除,即通过字节码是无法判断源代码是否为泛型的,有了此属性做标记,在执行反射时就能获取泛型信息
- BootstrapMethods属性:保存invokedynamic指令的引导方法,详情待以后补上。