目录
Class类文件
Class类文件即java文件编译之后得到的字节码文件,一个Class文件由下面这几个部分组成
名称 |
魔数,Class文件版本 |
常量池 constant_pool |
访问标志 |
类索引,父类索引,接口索引集合 |
字段表集合 |
方法表集合 |
属性表集合 |
专门用于分析Class文件字节码的工具:javap,可以输出class文件的字节码内容:
javap -verbose Test.class
魔数,Class文件版本
每个Class文件的头4个字节称为魔数(magic),它的唯一作用是判断该文件是否为一个能被虚拟机接受的Class文件。它的值固定为0xCAFEBABE。紧接着magic的4个字节存储的是Class文件的次版本号和主版本号,高版本的JDK能向下兼容低版本的Class文件,但不能运行更高版本的Class文件
常量池
常量池中主要存放两大类常量:字面量和符号引用
字面量: 字面量比较接近于Java层面的常量概念,如文本字符串、被声明为final的常量值等
符号引用包含三个部分:
- 类和接口的全限定名(即带有包名的Class名,如:org.xx.test.TestClass)
- 字段的名称和描述符(private、static等描述符)
- 方法的名称和描述符(private、static等描述符)
访问标志
在常量池结束后,紧接着的2个字节代表访问标志(access_flags),这个标志用于识别一些类或借口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等
类索引、父类索引与接口索引集合
类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interfaces)是一组u2类型的数据的集合,Class文件由这三项数据来确定这个类的继承关系。类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。由于Java语言不允许多重继承,所以父类索引只有一个,除了java.lang.Object之外,所有的Java类都有父类,因此除了java.lang.Object外,所有Java类的父类索引都不为0。接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句(如果这个类本身是一个接口,则应当是extends语句)后的接口顺序从左到右排列在接口的索引集合中。
类索引、父类索引和接口索引集合都按顺序排列在访问标志之后,类索引和父类索引用两个u2类型的索引值表示,它们各自指向一个类型为CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型的常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串。
对于接口索引集合,入口的第一项—u2类型的数据为借口计数器(interfaces_count),表示索引表的容量。如果该类没有任何借口,那么该计数器值为0,后面的接口的索引表不再占用任何字节。
字段表集合
字段表(field_info)用于描述接口或类中声明的变量。字段(field)包括了类级变量或实例级变量,但不包括在方法内部声明的变量。java中描述一个字段可以包含的信息:字段的作用域(public、private、protected修饰符)、是类级变量还是实例级变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否强制从主内存读写)、可否序列化(transient修饰符)、字段数据类型(基本类型、对象、数组)、字段名称。这些信息中,各个修饰符都是布尔值,要么有某个修饰符,要么没有,很适合使用标志位来表示。而字段叫什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述。
字段修饰符放在access_flags项目中,它与类中的access_flags项目是非常类似的,都是一个u2的数据类型,其中可设置的标志位和含义如下
跟随access_flags标志的是两个索引值:name_index和descriptor_index。它们都是对常量池的引用,分别代表着字段的简单名称及字段和方法的描述符。
全限定名:”org/fenixsoft/clazz/TestClass”是类的全限定名,仅仅是把类全名中的“.”替换成了“/”而已,为了使连续的多个权限定名之间不产生混淆,在使用时最后一般会加入一个“;”号表示全限定名结束。
简单名称:指没有类型和参数修饰的方法或字段名称,例如方法inc()和字段m的简单名称分别是“inc”和“m”。
描述符:作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型(byte、char、double、float、int、long、short、boolean)及代表无返回值的void类型都用一个大写字符来表示,而独享类型则用字符L加对象的全限定名来表示:
方法表集合
Class文件存储格式中对方法的描述与对字段的描述几乎用了完全一致的方法,方法表的结构如同字段表一样,依次包括了访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表合集(attributes)
因为volatile关键字和transient关键字不能修饰方法,所以方法表的访问标志中没有了ACC_VOLATILE标志和ACC_TRANSIENT标志。相对的,synchronized、native、strictfp和abstract关键字可以修饰的方法,所以方法表的访问标志中增加了ACC_SYNCHRONIZED、ACC_NATIVE、ACC_STRICTFP和ACC_ABSTRACT标志
方法的定义可以通过访问标志、名称索引、描述符索引表达清楚,但方法里面的代码在哪?方法里的Java代码,经过编译器编译成字节码指令后,存放在方法属性表集合中一个名为“Code”的属性里面,属性表作为Class文件格式中最具扩展性的一种数据项目。
属性表集合
属性表(attribute_info)在前面的讲解之中已经出现过多次,在Class文件、字段表、方发表中都可以携带自己的属性表集合,以用于描述某些场景专有的信息。
与Class文件中其他的数据项目要求严格的顺序、长度和内容不同,属性表集合的限制稍微宽松,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java虚拟机运行时会忽略掉它不认识的属性
为了能正确地解析Class文件,预定义了9项虚拟机实现应当能识别的属性:
对于每个属性ÿ