深入理解Java虚拟机——第六章——类文件结构
Java虚拟机不和包括Java在内的任何语言绑定,它只与Class文件这种特定的二进制文件格式所关联。
Class类文件结构
任何一个Class文件都对应着一个唯一一个类或接口的定义信息。但反过来说,类或接口不一定都得定义在文件里(可通过类加载器直接生成)。
Class文件是一组以8位字节为基础单位的二进制流。当数据占用8位字节以上,则会按照高位在前的方式(高位字节在地址最低位,低位字节在地址最高位。地址是从低到高的。)分割成若干个8位字节进行存储。
Class文件格式采用一种类似C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号数和表:
- 无符号数:属于基本数据类型,以u1、u2、u4、u8分别代表1、2、4、8个字节的无符号数。无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成的字符串值。
- 表:由多个无符号数或者其他表作为数据项构成了复合数据类型。所有表都习惯性以“_info”结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质上就是一张表。
魔法数与Class文件的版本
每个Class文件的头4个字节称为魔法数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。Class文件的魔法书为:0xCAFEBABE。
紧接着魔法数的4个字节是Class文件的版本号:第5、6个字节是次版本号,第7、8个字节是主版本号。高版本JDK能向下兼容以前版本的Class文件,Java版本号从45开始的。
常量池
常量池的常量数量不固定,在其入口放置u2类型的数据,代表常量池容量计数值。但其计数是从1开始的而不是0,0是用于表达指向常量池的索引值的数据为“不引用任何一个常量池项目”的含义。只有常量池的容量计数是从1开始。
常量池主要存放两大类常量:字面量和符号引用。字面量如文本字符串、声明为final的常量值等。符号引用包括下面三类常量:类和接口的全限定名(Fully Qualified Name)、字段的名称和描述符(Descriptor)、方法的名称和描述符。
在Class文件不会保存最终内存布局信息,即不经过运行期间转换的话不会得到真正内存入口地址。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。
方法和字段的最大长度就是u2类型所能表达的最大值65535,即2^16=2^6 * 2^10=64 * 1024=64KB英文字符。
javap用于分析Class文件字节码。javap -verbose XXX(.class文件的文件名)。
访问标志
常量池结束后紧接着两个字节代表访问标志。用于识别一些类或接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类,是否被声明为final等。
访问标志的值是用|得到的,比如public为0x0001,接口为0x0200,则其结果为0x0001|0x0200=0x0201。
类索引、父类索引与接口索引集合
类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。接口索引集合就用来描述这个类实现了哪些接口,按implements的顺序。
三者都排在访问标志之后,各自指向一个类型为CONSTANT_Class_info的类描述符常量。
字段表集合
字段表(filed)包括类级变量以及实例级变量,但不包括方法内部声明的局部变量。
可包括的信息有:字段的作用域(public、private、protected)、是实例变量还是类变量(static)、可变性(final)、并发可见性(volatile)、可否被序列化(transient)、字段数据类型、字段名称。
全限定名:包名加类名,如“org/ou/Class”。
简单名称:没有类型和参数修饰的方法或者字段名称。如fun()方法的fun,num字段的num。
描述符:描述字段的数据类型,方法的参数列表(数量、类型以及顺序)和返回值。(基本数据类型基本都是大写的首字母,除了long是J,对象类型是L)。
用描述符描述方法时,先参数列表后返回值。参数列表按参数的严格顺序放在小括号“()”之内。如String inc()为“()Ljava/lang/String”,void inc()为“()V”。
字段表集合不会出现从超类或者父接口中继承而来的字段,但有可能出现原来Java代码中不存在的字段,譬如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。
在Java中字段是无法重载的,两个字段的数据类型、修饰符不管相不相同都必须使用不一样的名称,而对于字节码,两个字段的描述符不同那字段重名就合法(没懂,字段就是类成员,描述符不同不就是数据类型或修饰符不同,为什么就可以重名。这里想表示的应该是方法和字段名)。
方法表集合
包括访问标志、名称索引。描述符索引。属性表集合。
方法里的代码存放在方法属性表集合中一个名为“Code”属性里面。属性表是Class文件格式中最具扩展性的一种数据项目。
如果父类方法在子类中没有被重写,方法表集合中就不会出现来自父类的方法信息。
Java中,重载方法,除了要与原方法具有相同的简单名称外,还需要有一个与原方法不同的特征签名。特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是返回值不会包含在特征签名中。因此Java无法依靠返回值不同来进行重载。但在Class文件只要描述符不是完全一致的两个方法也可以共存,即返回值不同也可共存(这应该只是按规则能共存,但实际上不会出现)。经测试,参数顺序不同可重载,是看参数数据类型而不是参数名称。
属性表集合
字段表、方发表都可以由自己的属性表集合,用于描述某些场景专有的信息。
Slot是虚拟机为局部变量分配内存所使用的最小单位。除了double和long这两种64位数据需要两个Slot外,其它都占用1个Slot。
没有局部变量其参数值也是1,因为在任何实例方法里,都可以通过this关键字访问到此方法所属的对象。
未完待续。。。。。。