我是一个class文件。我的内部是由一个被叫做ClassFile的structure组成。
我在jvm中占有很重要的地位,你可去看看jvm规范中我占了多少篇幅,告诉你,足足有大半本书!第四章,标题叫做 The class File Format。可以这么说。你把编译器、字节码、jit那些看过以后,再把我搞清楚,基本上jvm你也就精通了。鉴于我这么重要,今天我介绍下自己。我长下面这样:
ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }
你也许对这个结构有点疑问,怎么长得这么像c语言的结构体。没错,我们这里就是采用类似c语言结构体的伪结构来表示一个class file的结构。为了说明内部的细节,我们使用了这个伪结构。其实我真实的结构是一组以8位字节为基础单位的二进制流,包含多个数据项,各个数据项严格按照顺序紧凑的排列在Class文件中。其实封面才是我们本来的面目。
为了更友好的说明内部细节,我们还是回到上面那个伪结构,看了这个结构,你也许在纳闷这些u2、u4都是什么鬼。放在一个属性名的前面,有点像类型。没错,基本就是这个意思。在我们classfile的世界上,我们使用一组专用的数据类型来表示class文件的内容。比如u1就代表1个字节的无符号数(unsigned quantity)。u2、u4同理。
在java se platform中,这种类型可以通过java.io.DataInput这个接口中的方法readUnsignedByte,readUnsignedShort, and readInt来读取。
这会你也许又会问那夹杂在u2和u4中间的那些cp_info、field_info以及attribute_info又是什么鬼。其实这个你可以想象下在.java文件中的entity。
什么userinfo啦,orderinfo啦。这里我们把这个结构叫做table。也就是类似数据库里的表结构的样子。嗯,没错。在classfile的世界里,有两种类型,一种就是之前说的那种无符号数,另外一种就是这里的table类型。你再看看这些名字,也就知道大概存储什么东西啦。一个class file结构。肯定得存储这个类有哪些字段吧,那么就存储到field_info这个表里。肯定得存储有哪些方法吧,那么就存储到method_info这个表里。这里边有个cp_info这个是存储什么呢?这个是一个常量池。这个里边存储了方法名、类名、接口名以及源码中那些真正的常量。
其它的项涉及到这些 名称都是存一个常量池的一个索引值。也就是一个引用。
这会你也许又要问。uX和*_info后面那些类似字段名称的东西是什么意思呢。为了避免与类的字段和类的实例等概念产生混淆,这里我们给这些类似字段的东东起了一个新的名词叫:项。英文叫item。没错,再读一遍:item。(ps:好像实在想不起起什么名字,最后都叫做item。就像中文一样,不知道叫什么的时候,就叫“那个东西”、“那个人”)。。。。。起名字真的真的不容易。洪荒之力啊。回归正题!
magic (魔数)
先来来看magic是什么“东西”?在Class文件结构中,最头的4个字节用于存储魔数Magic Number,用于确定一个文件是否能被JVM接受。看起来他唯一的作用也就是jvm用来放行的门禁卡。这个number的值固定就是0xCAFEBABE。。。。。。。。。
minor_version、major_version
好,现在来看看minor_version和major_version。也许你一看就知道了。这两个是class文件的格式版本号。和我们现在说的版本管理基本一个概念。
major就是大版本。minor就是小版本。比如2.1。major_version就是2。minor_version就是1。
constant_pool_count
在版本号之后,就是constant_pool_count。这个是常量池计数器。它的值等于constant_pool中的成员数加1。
constant_pool[constant_pool_count-1]
那么constant_pool前面已经说到了一部分。这里边包含了class文件结构和其子结构中引用的所有字符串常量,以及类名、接口名、字段名和其他常量。
access_flags
access_flags则表示访问标志。下表是class文件的访问标志们:
Flag Name Value Interpretation ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package. ACC_FINAL 0x0010 Declared final; no subclasses allowed. ACC_SUPER 0x0020 Treat superclass methods specially when invoked by the invokespecial instruction. ACC_INTERFACE 0x0200 Is an interface, not a class. ACC_ABSTRACT 0x0400 Declared abstract; must not be instantiated. ACC_SYNTHETIC 0x1000 Declared synthetic; not present in the source code. ACC_ANNOTATION 0x2000 Declared as an annotation type. ACC_ENUM 0x4000 Declared as an enum type.
带有ACC_INTERFACE标志的class文件表示是一个接口,而不是一个类,如果没有这个标志的则表示是一个类而不是接口。这里你也许会纳闷,奇怪了,访问标志怎么还会混入一些class的类型内容,这个应该再新增一个项啊。比如叫item_type。不过既然标准就是这么定义的。我们知道就是了。
另外如果一个class文件被设置了ACC_INTERFACE,那么同时也要被设置ACC_ABSTRACT标志。同时就不能再去设置ACC_FINAL、ACC_SUPER或ACC_ENUM了。这个也很好理解,就是互斥。
如果没有设置ACC_INTERFACE标志,那么这个class文件可以具有上面这个表中除了ACC_ANNOTATION外的其它所有的标志。
但要要注意的是,ACC_ABSTRACT和ACC_FINAL是互斥的,你懂的,既然抽象了就必须被用来继承,如果再加个final,就没法被继承了。所以是矛盾的,互斥的。
你也许看到了一个奇葩的ACC_SUPER。是不是以你的编程经验怎么都猜不出来这个是干嘛的?没错,有这种感觉就对了。因为这个标志是一种为了弥补jvm设计上的一些过错而增加的。说说这个故事吧。在jdk1.02之前,有个叫invokenonvirtual的指令。在1.02后,这个指令被改名叫做invokespecial。invokenonvirtual的时候没有invokespecial那样只允许调用superclass、private方法或<init>方法。于是在所有的1.02后的class 都必须设置ACC_SUPER这个标志,来表明强加给invokespecial的新的约束必须要被遵守。在1.02 之前ACC_SUPER是没什么鸟用的,甚至就没有ACC_SUPER这个标志。 一般情况下, 调用构造方法或者使用super关键字显示调用父类的方法时, 会使用这条字节码指令。 这正是ACC_SUPER这个名字的由来。 在java 1.2之前, invokespecial对方法的调用都是静态绑定的, 而ACC_SUPER这个标志位在java 1.2的时候加入到class文件中, 它为invokespecial这条指令增加了动态绑定的功能。
好,现在再来说说ACC_SYNTHETIC,此标志意味着该类或接口是由编译器生成的,而不是由源码生成的。
另外ACC_ANNOTATION也很简单。annotation类型必须设置这个值。如果设置了ACC_ANNOTATION,那么也必须设置ACC_INTERFACE标志。
ACC_ENUM标志标明该类或其父类为枚举类型。
access_flags终于搞清楚了。现在来看看
this_class这个项。这个项的值必须是上面的那个常量池中某一项的有效索引值。常量池在这个索引处的成员必须是CONSTANT_Class_info结构。来表示这个class 文件中所定义的类或接口。
super_class(父类索引)
表示这个class文件所定义的类的直接父类。也是上面的常量池某一项的有效索引值。而且结构也必须是CONSTANT_Class_info结构。
interfaces_count (接口计数器)
这个项的值表示当前类或接口的直接超接口的数量。
interfaces[] (接口表)
就是这个类所实现的接口。里边同样是常量池的索引值。接口表里边的顺序和源代码的接口顺序是一致的。
fields_count (字段计数器)
fields_count项的值是当前类文件fields表中field_info结构体的数量。
fields[] (字段表)
在fields中的每个值都必须是field_info结构。表中的每个值都完整的描述了该类或接口中的一个字段。fields表中只包含当前类或接口中的字段,不包含超类或super 接口中的字段。
methods_count (方法计数器) methods_count项的值是当前类文件方法表中method_info结构体的数量。
也就是类文件中方法的数量。
methods[] (方法表)
methods表中的每个成员都必须是一个method_info结构,用来表示当前类或接口中的某个方法的完整描述。
attributes_count (属性计数器) attributes_count的值是该类中的attributes表中属性的数量
attributes[] (属性表)
同样的上面的各种表一样。里边的每个值都必须是attribute_info结构。这个属性也许你理解起来有点抽象,不知道是什么。这时候你可以想想一个类文件结构那么复杂。比如一个方法中那么多代码是怎么表示的呢?其实这些事情就是由jvm中预定义好的属性来表示的。 jvm中有很多的预定义属性。其实也是一个结构。类似今天class file的结构。你可以这样认为,一个类类或接口这么复杂。就像我们做一个应用一样,需要设计很多数据表来实现数据存储。你可以把这些预定义属性理解为jvm中的数据表。有关属性的内容我们以后会专门抽出一期来讲解这方面的内容。
好了,鉴于公号文章篇幅限制,况且你也一下子看不完几十页,本文就介绍这么多。希望通过这封介绍信能够让你了解我!了解JVM!作为吃瓜群众,今天还有更重要的事情要做!