[size=large]一.概述[/size]
[size=small]
我们在写JAVA程序的时候,面对的都是.java文件的编写,但是JVM不会直接拿.java文件去执行的,对于JVM而言,它只认识.class文件,那么是不是后缀名是.class的文件JVM都会接收呢?显然不是,因为要想JVM接受一个.class文件,这个.class文件必须要有严格的结构,这样JVM才会识别这个.class文件,并且按照.class文件去做一些事情。
我们写的.java文件首先要经过JAVA编译器的编译,这时候编译的结果就是.class文件,JAVA语言有它自己的语法产生式,编译的时候,首先对源程序进行此法分析,然后再在词法分析的基础上按照JAVA语言自己的语法产生式进行语法分析,语法分析的目的是按照产生式对源程序(这里源程序其实就是一个字符串)进行一系列推导,比如,最左推导,最右推导等等,如果无法按照产生式推导出这个字符串,这就说明源程序有语法错误。如果源程序没有语法错误,此时JAVA编译器就是生成目标代码,其实就是.class文件,这时候这个.class文件有非常严格的结构,能够被JVM所识别,其实.class文件就是javac的结果,同样也有一个javap命令可以实现反编译,javap可以把一个.class文件中各个结构清晰的打印出来,方便开发人员和.java文件进行对比。假如有一个Test.class文件,我们是由javap -verbose Test就可以看到这个class文件各个部分的具体结构了。这里的主要目的就是分析class文件的结构以及怎么去实现一个javap这样的命令。
[/size]
[img]http://dl.iteye.com/upload/attachment/0081/7608/bdd00900-0871-3ddd-ae48-4539473b5b69.png[/img]
[size=small]
上图是针对上面描述的一个图示概述
[/size]
[size=large]二.class文件的整体数据结构[/size]
[img]http://dl.iteye.com/topics/download/302ef717-d56c-3f1d-85b0-d7c5a9d56980[/img]
[size=small]
通过观察上图,我们发现java源程序中的所有信息都可以在class文件中体现出来(注释除外),每个class文件最后都有一些附加属性,这些附加属性是针对这个class的描述。比如当前class对应的源程序文件名称,当前class的内部class等等都属于class的附加属性,出现在class文件的最后。
[/size]
[size=small]下面我们给出class文件的数据结构表,可以参考[url]http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html[/url][/size]
[table]
|类型|名称|数量|
|u4|magic(魔数)|1|
|u2|minor_version(版本号之小版本号)|1|
|u2|major_version(版本号之主版本号)|1|
|u2|constant_pool_count(常量池中常量的数目)|1|
|cp_info|constant_pool(常量池)|constant_pool_count - 1|
|u2|access_flags(当前类的访问权限标记)|1|
|u2|this_class(当前类的相关描述信息)|1|
|u2|super_class(当前类超类的描述信息)|1|
|u2|interfaces_count(当前类实现的接口数目)|1|
|u2|interfaces(当前类实现了那些接口)|interfaces_count|
|u2|field_count(当前类的字段数目)|1|
|field_info|fields(当前类的字段)|field_count|
|u2|method_count(当前类的方法数目)|1|
|method_info|methods(当前的方法)|method_count|
|u2|attributes_count(当前类的附加属性数目)|1|
|attribute_info|attributes(当前类的附加属性)|attributes_count|
[/table]
[size=small]注:[/size]
[size=small]
1.上述数据结构中既有无符号数,也有表,所有的表都以_info结尾,表用于描述有层次关系的复合数据结构,无论是无符号数还是表,当需要描述同一类型但数量不定的多个数据时,经常会使用一个前置容量计数器加若干连续的数据项的形式,这时候称这一系列连写的某一类型的数据为某一类型的集合。
[/size]
[size=small]
2.对于class文件的这么多结构,其实一开始没有必要担心的也没有必要仔细去深究这些结构的之间的关系,先有个大致的了解,慢慢往后一个一个数据结构去理解,最后再回过头来看看这些数据结构,你就能理解他们之间的关系,以及表的内部是如何组织的了。
[/size]
[size=large]三.魔数和版本号的解析[/size]
[size=small]
1.关于魔数的解析
在.class文件的开头四个字节就是.class文件的魔数,这个魔数决定了这个.class文件能否被JVM虚拟机所加载。首先读取这个字节数组,这个字节数组大小是4,然后把这个字节数组转换成一个十六进制的数字,看看这个十六进制的数字是不是0XCAFFEEBABE,如果这个字节数组对应的十六进制数是0XCAFFEEBABE,这时候这个.class文件就可以被JVM所加载,否则这就是一个非法的.class文件。在这里需要注意的是一个字节表示8个二进制为,那么四个字节就是32个二进制位了。
[/size]
[size=small]
2.关于版本号的解析
紧接着魔数后面就是版本号,这个版本号其实和JDK的版本有关,分为次版本号和主版本号,各占一个字节,把这个字节转换成十进制数字,就可以看到版本号是多少了。
[/size]
[size=small]
3.关于魔数和版本号的解析,说白了就是字节数组到十六进制或者十进制数值之间的转换。
[/size]
[size=small]
4.下面用vi打开一个.class文件,然后使用%!xxd切换到十六进制显示,可以看看魔数和版本号
[/size]
[img]http://dl.iteye.com/upload/attachment/0081/7640/462f1c6b-de0c-3e2d-b002-2067605a59a6.png[/img]
[size=large]四.常量池的解析[/size]
[size=small]
1.关于常量池的概述
常量池是一个很重要的部分,首先从字面意思来理解,常量即不变,池说明是一个容器,在这个容器中存放的都是一些不变的量,把这些不变的量集中在一个地方,方便其他模块引用,这也是写程序时的一个基本理念。在常量池中主要存放了两大类常量:字面量(Literal)和符号引用(Symbolic Reference)。字面量比较接近JAVA语言层面的常量,如文本字符串,被定义为final类型的常量值等等。而符号引用和编译原理有关,编译JAVA代码的时候需要一个符号表,这个符号表中存储了对于方法和字段以及类描述等信息。每个方法都有一些固定的属性,例如方法名称,返回值类型,入参类型等等,这些信息在编译的过程存储在符号表中,在CLASS文件中就体现在常量池中。
[/size]
[size=small]
2.常量池中内容概述图
[/size]
[img]http://dl.iteye.com/upload/attachment/0084/5293/a3ad39ab-5f55-3a04-944b-b576b0d4ca1b.bmp[/img]