Class文件结构

Class文件是由Java语言或者其他语言编译而得到的,然后再交给JVM去加载和执行。对于Class文件,我们可以通过文本编辑器查看它的16进制编码,然后分析它的组成构造。下面我们看一下Class文件包含哪些模块:

对于Class字节码文件的查看,最初我们会用16进制的文本编辑器查看。但是这种方式并不常用,一般情况下我们会使用javap -verbose XX.class的命令来查看Class结构。当然更好的方式是通过第三方工具(jclasslib)来查看。下面我们来详细的介绍一下具体的结构。

魔数

魔数是Class文件的LOGO标识。也就是当使用16进制文本编辑器查看Class文件时,开头的字符是:CAFEBABE

版本号

Class文件的版本号包含了:主版本号和次版本号。两个信息分别占用了2个字节的大小,并且紧跟在CAFEBABE(魔数)后面,依次为次版本号、主版本号。下面为用工具查看的结果:

下面列出了版本号和编译器版本的对应关系:

常量池

常量池中的信息比较丰富,这个类中的基础信息都存放在里面。其中主要包含了:字面量(Literal)和符号引用(Symbolic References)两大类。主要包含了以下信息:

类和接口的全限定名

字段的名称和描述符

方法的名称和描述符

在工具中查看的效果如下所示,其中我们从General Information中能看到常量池的大小是19,而Constant Pool中只有18个。这个是因为index为0的未展示出来,用作一些特殊处理使用。

常量池的类型主要包含以下类型:

访问标志

紧跟在常量池之后的就是访问标志(access_flags),这个标志用于识别一些类或者接口层次的信息。其中包括了:是类还是接口;是否定义为public;是否为abstract类型;是否为final等信息。具体的标志位和含义如下所示:

当前类、父类和接口

紧跟着访问标志的是:当前类、父类和接口的信息,如下所示:

字段表集合

紧跟其后的是:类的属性字段信息。如下所示:

字段的访问权限标志如下表格所示:


方法表集合

方法表集合和字段表类似,依次包括了:访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)。如下图所示:

对于方法表集合的访问标志,有如下表格:

属性表集合

这里面的属性代表的并不是Java类中的字段属性,而是Class文件、字段表、方法表表中各个模块所携带的属性。因为属性相对较多,使用的位置也不尽相同,下面只列举一下常见的一些属性。

Code属性

Code属性位于方法表的每个方法之中,其中包含了方法执行的字节码指令、异常处理等信息。如下所示:

LineNumberTable属性

该属性位于Code属性表中,存放了字节码指令对应到.java文件中的哪一行代码。如下图所示:

上图中的对应关系表示的是:字节码指令偏移地址为0的字节码,对应的代码行数是第8行。

LocalVariableTable属性

LocalVariableTable属性用于描述栈帧中局部变量表的变量和Java源码中的变量之间的关系。它不是运行时必须的属性,可以通过-g:none或-g:vars来取消这项信息的生成。如果取消这部分信息,在IDE中的方法提示信息的入参的参数名称,将表现为arg0、arg1等字样。

SourceFile属性

SourceFile属性用于记录生成Class文件的源文件的名称。可选属性,可以通过-g:none或-g:source来开启/关闭生成。如下图所示:

InnerClasses属性

InnerClasses属性用于记录内部类与宿主类之间的关联。如果一个类内部定义了内部类,就会产生InnerClasses属性。如下所示:

Deprecated属性

Deprecated属性可以用在类、方法、字段等结构中,用于表示类、方法、字段在未来的版本中有可能废弃。其中对于方法的废弃如下所示:

总结

对于Java字节码的构成,上面只是简单的介绍一下,仅仅是对于Class结构有个整体的认识。如果想要详细的了解Class文件结构,请参阅《Java虚拟机规范(Java SE 8版)》。不过在日常的Class文件查看中,还是借助工具查看比较直接方便。


参考:《深入理解Java虚拟机》