一 class文件与java语言的关系
(1) 一个class文件中只能包含一个类或者接口
(2)java程序可以通过编译器编译成class字节码,也可以被编译成其他二进制文件格式
(3)其他语言的程序也可以通过编译器编译成class字节码
(4)class字节码提供了比java语言更强大的语义
(5)■自己的理解: 虽然java中只支持有符号数,但在class文件中,是使用无符号数的
有符号数是指如32位的int的最高位是符号位,这就限定了负数到正数的范围,无符号数是指将32位全部都表示数位,只能表示非负数,但是在class文件里如何表示负数呢
二 Class文件格式
1 magic(魔数) : 以 0xCAFEBABE 4个字节开头的文件才可能是class文件
2 紧接着魔数的4个字节是次版本号,主版本号,java编译器编译的class文件是有某种版本号的,也是虚拟机要支持的class文件版本号
3 constant_pool_count 和 constant_pool
(1)constant_pool_count 的值包含了自己,可以将constant_pool_count本身看成是索引为0的项,而真正的constant_pool项是从索引1开始的,所以constant_pool的数量是constant_pool_count值减1
(2)可将constant_pool的索引看成是指示某个常量池入口(即每个常量池起始点)的下标,而每个常量池入口都从一个长度为一个字节的常量池标志开始,这个标志指出了本常量池常量的类型,★虚拟机根据常量池的类型按正确的操作加载常量. 常量池即是info表,常量池名/表名通过在标志名后加上_info产生,★一个常量池只保存一个相关类型的常量信息,而且常量池中的真正的常量信息可能要通过索引到另一个常量池才能获取到.对应于各种不同常量池入口的表将在后面说明.
4 access_flags
紧接着常量池后的两个字节称为access_flags,它是通过在特定位置1来表示访问修饰符的,所有未使用的位都必须由编译器置0,而且java虚拟机必须忽略它.
5 this_class
接下来两个字节为this_class项,它是一个对常量池的索引,常量池入口是CONSTANT_Class_info表,而CONSTANT_Class_info表又由两部分组成: 常量池标志和name_index,常量池标志即是CONSTANT_Class对应的值,name_index又是另一个常量池的索引,对应的常量池入口为一个包含了类或接口全限定名的CONSTANT_Utf8_info表.最终可通过这个CONSTANT_Utf8_info表,找到类或接口全限定名的Utf8字符串的字节数据,即是类自身对应的全限定名.
6 super_class
紧接在this_class之后的是一个两字节的super_class项,它也是一个对常量池的索引,除了Object类以外(Object类没有父类),常量池索引super_class对于所有的类均有效,对于Object类,super_class的值为0.对于接口,接口可以extends多个接口,但在常量池入口super_class位置的项为java.lang.Object,具体的索引形式可类比this_class
7 interfaces_count和interfaces
(1)紧接着super_class的是interfaces_count,表示在文件中由该类直接实现或者由接口所扩展的父接口的数量.
(2)然后是名为interfaces的数组,按序包含了(从左到右)类直接实现,或者接口直接扩展的父接口的常量池索引(CONSTANT_Class_info入口位置)
8 fields_count和fields
(1)紧接在interfaces后面的是对在该类或者接口中所声明的字段的描述,注意不列出从超类或父接口继承而来的字段.
(2)fields_count包含了类变量和实例变量的字段的数量总和
(3)在fields_count后面是不同长度的field_info表的索引,★注意field_info表并不是常量池,第个field_info表都展示了一个字段的信息,此表包含了字段的名字,描述符和修饰符.如果该字段被声明为final,field_info表还会展示其常量值.
(4)fields还可能会包含在对应的java源文件中没有叙述的字段,例如,对于一个内部类的fields列表来说,为了保持对外围类实例的引用,java编译器会为每个外围类实例添加实例变量,★这些字段使用Synthetic属性标识
9 methods_count和methods
methods_count指示紧随其后的methods列表中method_info表的数量(★注意它也不是常量池),method_info表中包含了与方法相关的信息.
10 attributes_count和attributes
class文件中最后的部分是属性attribute,每个attribute_info表的第一项是指向常量池中CONSTANT_Utf8_info的索引,指示了属性的名称.
属性出现在class文件中的多处,而不仅仅在顶层classFile表的attributes项中出现,出现在classfile表中的属性主要给出了与文件中所定义的类和接口相关的信息;出现在field_info表中的属性主要给出了与字段相关的信息;出现在method_info表中的属性主要给出了与方法相关的信息.
java虚拟机实现定义了两种属性: sourcecode 和 innerclasses, 它们出现在classfile表中属性列表中.
三 特殊字符串
1 ■常量池中容纳的符号引用对应的字符串,是不是都放在CONSTANT_Utf8类型的CONSTANT_Utf8_info表中,而且在下面所说的2,3点情形中,例如,在类C中调用方法method1与在类C中定义方法method1使用的方法的简单名称和方法描述符对应的特殊字符串,是可以共享的
2 常量池中容纳的符号引用包括三种特殊的字符串:全限定名、简单名称和描述符。所有的符号引用都包括类或者接口的全限定名。字段的符号引用除了全限定类型名外,还包括简单字段名和字段描述符。方法的符号引用除了全限定类型名外,还包括简单方法名和方法描述符。
3 在符号引用中使用的特殊字符串也同样用来描述被class文件定义的类或者接口。例如,定义过的类或者接口会有一个全限定名。对于每一个在类或者接口中声明的字段,常量池中都会有一个简单名称和字段描述符。对于每一个在类或者接口中声明的方法,常量池中都会有一个简单名称和方法描述符。
4 三种特殊的字符串
(1)全限定名
当常量池入口指向类或者接口时,它们给出该类或者接口的全限定名。在class文件中,全限定名中的点用斜线取代了。例如,在class文件中,java.lang.Object的全限定名表示为java/lang/Object;在class文件中,java.util.Hashtable的全限定名表示为java/util/Hashtable。
(2)简单名称
字段名和方法名以简单名称(非全限定名)形式出现在常量池入口中。例如,一个指向类java.lang.Object所属方法StringtoString()的常量池入口有一个形如“toStirng”的方法名。一个指向类java.lang.System所属字段java.io.PrintStream.out的常量池入口有个以形如”out”的字段名。
(3)描述符
除了类(或接口)的全限定名和简单字段(或方法)名,指向字段和方法的符号引用还包括描述符字符串。字段的描述符给出了字段的类型;方法描述符给出了方法的返回值和方法参数的数量、类型以及顺序。
需要注意的是实例方法的方法描述符并没有包含作为第一个参数被传给所有实例方法的隐藏this参数,但所有调用实例方法的java虚拟机指令都会隐式传递thsi参数.并且方法描述符只能包含255个字长以内的参数,传给实例方法的隐藏this参数引用占用一个字长,属于基本类型的long或者double占用两个字长,其他的参数占用一个字长.
四 常量池
1 CONSTANT_Utf8_info表
可变长度的CONSTANT_Utf8_info表使用一种UTF-8格式的变体来存储一个常量字符串。这种类型的表可以存储多种字符串,包括:
文字字符串,如String对象。
被定义的类和接口的全限定名。
被定义的类的超类(如果有的话)的全限定名。
被定义的类和接口的父接口的全限定名。
由类或者接口声明的任意字段的简单名称和描述符。
由类或者接口声明的任意方法的简单名称和描述符。
任何引用的类和接口的全限定名。
任何引用的字段的简单名称和描述符。
任何引用的方法的简单名称和描述符。
与属性相关的字符串。
2
CONSTANT_Integer_info表
CONSTANT_Float_info表
CONSTANT_Long_info表
CONSTANT_Double_info表
(1)它们都是固定长度的
(2)它们的格式都是由 tag + bytes 组成,tag是常量池标志值(每一个常量池都会有标志值以指明常量类型),bytes项按照高位在前的格式存储long类型的值
(3)Integer和Float占用4个无符号的byte(u4),而Long和Double占用8个无符号的byte(u8)
(4)一个Long和Double类型常量池入口紧接着下一个入口,但下一个入口的索引值却比紧挨着的上一个入口的值多2
3 CONSTANT_Class_info表
固定长度的CONSTANT_Class_info表使用符号引用来表述类或者接口,无论指向类,接口,字段,还是方法,所有的符号引用都包含一个CONSTANT_Class_info表.
由于java中的数组是完善的对象,CONSTANT_Class_info表也能够用来描述数组类,CONSTANT_Class_info表中的name_index项指向CONSTANT_Utf8_info表,该表中包含了数组的描述符,描述符可作为数组类的名称.由于java数组最多只能有255维,所以数组描述符最多只能有255个引导符"["
4 CONSTANT_String_info表
固定长度的CONSTANT_String_info表用来存储文字字符串值,该值亦可表示为类java.lang.String的实例.该表只存储文字字符串值,不存储符号引用.
5 CONSTANT_Fieldref_info表
(1)CONSTANT_Fieldref_info表描述了指向字段的符号引用.
(2)由三项组成: tag + class_index + name_and_type_index, class_index指向CONSTANT_Class_info表(可能是类或接口);★name_and_type_index指向CONSTANT_NameAndType_info表(用于指出字段的简单名称和字段的描述符)
(3)如果其他类的静态final字段使用编译时的常量进行初始化操作,那么class文件不包含对这些字段的符号引用.但是....见图示
6 CONSTANT_Methodref_info表
固定长度的CONSTANT_Methodref_info表使用符号引用来表述类中声明的方法(不包括接口中的方法).结构可类比CONSTANT_Fieldref_info表
7 CONSTANT_InterfaceMethodref_info表
固定长度的CONSTANT_InterfaceMethodref_info表使用符号引用来描述接口中声明的方法(不包括类中的方法).结构可类比CONSTANT_Methodref_info表
8 CONSTANT_NameAndType_info表
固定长度的CONSTANT_NameAndType_info表构成指向字段或都方法的符号引用的一部分(用于提供简单名称和描述符常量池的入口)
五 字段
在类或者接口中声明的每一个字段(类变量或者实例变量)都由class文件中的一个名为field_info的可变长度的表进行描述。
在一个class文件中,不会存在两个具有相同名字和描述符的字段。需要注意的是尽管在Java程序设计语言中不会有两个相同名字的字段存在于同一个类或者接口中,但一个class文件中的两个字段可以拥有同一个名字——只要它们的描述符不同。换句话说,尽管在程序设计语言中无法在同一个类或者接口中定义两个具有同样名字和不同类别的字段,但是两个这样的字段却可以同时合法地出现在一个Java class文件中
六 方法
在class文件中,每个在类和接口中声明的方法,或者由编译器产生的方法,都由一个可变长度的method_info表来描述。
同一个类中不能存在两个名字及描述符完全相同的方法。需要注意的是,在java程序设计语言中,尽管在同一个类或者接口中声明的两个方法不能有同样的特征签名(除返回类型之外的描述符),但在同一个class文件中,两个方法可以拥有同样的特征签名,前提是它们的返回值不能相同。
换句话说:在Java源文件的同一个类里,如果声明了两个具有相同名字和相同参数类型、但返回值不同的方法,这个程序将无法编译通过。在Java程序设计语言中,不能仅仅通过返回值的不同来重载方法。但是同样的两个方法可以和谐地在一个class文件中共存。
有可能在class文件中出现的两种编译器产生的方法是:实例初始化(名为<init>)和类与接口初始化方法(名为<clinit>)
七 属性
1 如同字段的field_info表或者方法的method_info表,一个属性由attribute_info表来组织.
2 method_info表中的第一项attribute_name_index指出了属性的名称,也即是它的"类型",这种方式就像cp_info(常量池info表)中由初始tag字节指出它们的类型一样,cp_info表的类型由一个无符号字节指定,而method_info表的类型由一个字符串指定
3 紧随attribute_name_index其后的是4字节长的attribute_length项,它给出除去起始6个字节,即除去attribute_name_index与attribute_length后整个attribute_info表的长度,因为只要遵循一定规则
,任何人都能够向java class文件中加入属性,所以长度是不可缺少的,这使用java虚拟机能够识别新属性,也能够路过无法识别的属性.
4 各种类型/名称的属性介绍
(1)Code属性:
定义了方法的字节码序列和其他信息. p161
(2)ConstantValue属性:
此属性出现在值为常量的字段的field_info表中,■在包含ConstantValue属性的field_info表内的access_flag中必须设定ACC_STATIC标志,还有可能存在ACC_FINAL标志.★当虚拟机初始化一个具有ConstantValue属性的字段时,它将一个常量值赋给这个字段,赋值操作在虚拟机调用声明此字段的类或者接口的初始化方法■之前进行■是<cinit>吗?
ConstantValue属性有一项是常量池索引,指向( CONSTANT_Integer_info表, CONSTANT_Float_info表, CONSTANT_Long_info表, CONSTANT_Double_info表, CONSTANT_String_info表)中的一项.
(3)Deprecated属性:
此属性存在于field_info,method_info和ClassFile表内的attributes项中,用来支持javadoc工具使用的文档注释中的@deprecated标志.编译器,虚拟机或者用来读取class文件的任何工具都可以使用
此属性来通知程序员方法或字段或类的禁用行为
(4)Exceptions属性:
此属性出现在每一个可能抛出已检出异常的方法的method_info表中,列出了方法可能抛出的异常.
(5)InnerClasses属性: p144
该属性用于记录内部类和宿主类之间的关系。如果一个类中定义了内部类,编译器将会为这个类与这个类包含的内部类生成InnerClasses属性
(6)LineNumberTable属性:
建立了方法字节码流偏移量和源代码行号之间的映射关系,可能会出现在Code_attribute表中的属性部分.
(7)LocalVariableTable属性:
建立了方法的栈帧中局部变量部分内容与源代码中局部变量的名称和描述符之间的映射关系,本属性可以(但不是一定)存在于Code_attribute表的属性部分中
(8)SourceFile属性
此属性可能存在于ClassFile表内的属性项中,它是一个可选的项,提供了产生class文件的源文件的名称
(9)Synthetic属性
此属性可能存在于field_info,method_info和ClassFile表内的attributes项中,它是一个可选的项,指明了为编译器所产生的字段,方法或者类型.未出现在源代码中的类成员必须使用Synthetic属性标注