澳门新葡萄京官网首页 68

澳门新葡萄京官网首页Java Class文件详解

Java
Class文件中包含以下信息:

实例分析Java Class的文件结构,实例分析java

学习Java的朋友应该都知道Java从刚开始的时候就打着平台无关性的旗号,说“一次编写,到处运行”,其实说到无关性,Java平台还有另外一个无关
性那就是语言无关性,要实现语言无关性,那么Java体系中的class的文件结构或者说是字节码就显得相当重要了,其实Java从刚开始的时候就有两套
规范,一个是Java语言规范,另外一个是Java虚拟机规范,Java语言规范只是规定了Java语言相关的约束以及规则,而虚拟机规范则才是真正从跨
平台的角度去设计的。今天我们就以一个实际的例子来看看,到底Java中一个Class文件对应的字节码应该是什么样子。
这篇文章将首先总体上阐述一下Class到底由哪些内容构成,然后再用一个实际的Java类入手去分析class的文件结构。

在继续之前,我们首先需要明确如下几点:

1)Class文件是有8个字节为基础的字节流构成的,这些字节流之间都严格按照规定的顺序排列,并且字节之间不存在任何空隙,对于超过8个字节的数据,将按
照Big-Endian的顺序存储的,也就是说高位字节存储在低的地址上面,而低位字节存储到高地址上面,其实这也是class文件要跨平台的关键,因为
PowerPC架构的处理采用Big-Endian的存储顺序,而x86系列的处理器则采用Little-Endian的存储顺序,因此为了Class文
件在各中处理器架构下保持统一的存储顺序,虚拟机规范必须对起进行统一。

2)
Class文件结构采用类似C语言的结构体来存储数据的,主要有两类数据项,无符号数和表,无符号数用来表述数字,索引引用以及字符串等,比如
u1,u2,u4,u8分别代表1个字节,2个字节,4个字节,8个字节的无符号数,而表是有多个无符号数以及其它的表组成的复合结构。可能大家看到这里
对无符号数和表到底是上面也不是很清楚,不过不要紧,等下面实例的时候,我会再以实例来解释。

明确了上面的两点以后,我们接下来后来看看Class文件中按照严格的顺序排列的字节流都具体包含些什么数据:

澳门新葡萄京官网首页 1

(上图来自The Java Virtual Machine Specification Java SE 7 Edition)

在看上图的时候,有一点我们需要注意,比如cp_info,cp_info表示常量池,上图中用
constant_pool[constant_pool_count-1]的方式来表示常量池有constant_pool_count-1个常量,它
这里是采用数组的表现形式,但是大家不要误以为所有的常量池的常量长度都是一样的,其实这个地方只是为了方便描述采用了数组的方式,但是这里并不像编程语
言那里,一个int型的数组,每个int长度都一样。明确了这一点以后,我们在回过头来看看上图中每一项都具体代表了什么含义。

1)u4 magic
表示魔数,并且魔数占用了4个字节,魔数到底是做什么的呢?它其实就是表示一下这个文件的类型是一个Class文件,而不是一张JPG图片,或者AVI的电影。而Class文件对应的魔数是0xCAFEBABE.

2)u2 minor_version
表示Class文件的次版本号,并且此版本号是u2类型的无符号数表示。

3) u2 major_version
表示Class文件的主版本号,并且主版本号是u2类型的无符号数表示。major_version和minor_version主要用来表示当前的虚拟
机是否接受当前这种版本的Class文件。不同版本的Java编译器编译的Class文件对应的版本是不一样的。高版本的虚拟机支持低版本的编译器编译的
Class文件结构。比如Java SE 6.0对应的虚拟机支持Java SE
5.0的编译器编译的Class文件结构,反之则不行。

4) u2 constant_pool_count
表示常量池的数量。这里我们需要重点来说一下常量池是什么东西,请大家不要与Jvm内存模型中的运行时常量池混淆了,Class文件中常量池主要存储了字
面量以及符号引用,其中字面量主要包括字符串,final常量的值或者某个属性的初始值等等,而符号引用主要存储类和接口的全限定名称,字段的名称以及描
述符,方法的名称以及描述符,这里名称可能大家都容易理解,至于描述符的概念,放到下面说字段表以及方法表的时候再说。另外大家都知道Jvm的内存模型中
有堆,栈,方法区,程序计数器构成,而方法区中又存在一块区域叫运行时常量池,运行时常量池中存放的东西其实也就是编译器长生的各种字面量以及符号引用,
只不过运行时常量池具有动态性,它可以在运行的时候向其中增加其它的常量进去,最具代表性的就是String的intern方法。

5)cp_info
表示常量池,这里面就存在了上面说的各种各样的字面量和符号引用。放到常量池的中数据项在The
Java Virtual Machine Specification Java SE 7 Edition
中一共有14个常量,每一种常量都是一个表,并且每种常量都用一个公共的部分tag来表示是哪种类型的常量。

下面分别简单描述一下具体细节等到后面的实例 中我们再细化。

CONSTANT_Utf8_info tag标志位为1,
UTF-8编码的字符串CONSTANT_Integer_info tag标志位为3,
整形字面量CONSTANT_Float_info tag标志位为4,
浮点型字面量CONSTANT_Long_info tag标志位为5,
长整形字面量CONSTANT_Double_info tag标志位为6,
双精度字面量CONSTANT_Class_info tag标志位为7,
类或接口的符号引用CONSTANT_String_info
tag标志位为8,字符串类型的字面量CONSTANT_Fieldref_info tag标志位为9,
字段的符号引用CONSTANT_Methodref_info
tag标志位为10,类中方法的符号引用CONSTANT_InterfaceMethodref_info
tag标志位为11, 接口中方法的符号引用CONSTANT_NameAndType_info tag
标志位为12,字段和方法的名称以及类型的符号引用

6) u2 access_flags 表示类或者接口的访问信息,具体如下图所示:
澳门新葡萄京官网首页 2

7)u2 this_class
表示类的常量池索引,指向常量池中CONSTANT_Class_info的常量

8)u2 super_class
表示超类的索引,指向常量池中CONSTANT_Class_info的常量

9)u2 interface_counts 表示接口的数量

10)u2
interface[interface_counts]表示接口表,它里面每一项都指向常量池中CONSTANT_Class_info常量

11)u2 fields_count 表示类的实例变量和类变量的数量

12) field_info
fields[fields_count]表示字段表的信息,其中字段表的结构如下图所示:

澳门新葡萄京官网首页 3

上图中access_flags表示字段的访问表示,比如字段是public,private,protect
等,name_index表示字段名
称,指向常量池中类型是CONSTANT_UTF8_info的常量,descriptor_index表示字段的描述符,它也指向常量池中类型为
CONSTANT_UTF8_info的常量,attributes_count表示字段表中的属性表的数量,而属性表是则是一种用与描述字段,方法以及
类的属性的可扩展的结构,不同版本的Java虚拟机所支持的属性表的数量是不同的。

13) u2 methods_count表示方法表的数量

14)method_info 表示方法表,方法表的具体结构如下图所示:

澳门新葡萄京官网首页 4
其中access_flags表示方法的访问表示,name_index表示名称的索引,descriptor_index表示方法的描述
符,attributes_count以及attribute_info类似字段表中的属性表,只不过字段表和方法表中属性表中的属性是不同的,比如方法
表中就Code属性,表示方法的代码,而字段表中就没有Code属性。其中具体Class中到底有多少种属性,等到Class文件结构中的属性表的时候再
说说。

15)
attribute_count表示属性表的数量,说到属性表,我们需要明确以下几点:

属性表存在于Class文件结构的最后,字段表,方法表以及Code属性中,也就是说属性表中也可以存在属性表属性表的长度是不固定的,不同的属性,属性表的长度是不同的

上面说完了Class文件结构中每一项的构成以后,我们以一个实际的例子来解释以下上面所说的内容。
复制代码 代码如下:
package com.ejushang.TestClass;
public class TestClass implements Super{
private static final int staticVar = 0;
private int instanceVar=0;
public int instanceMethod(int param){
return param+1;
}
}
interface Super{ }

通过jdk1.6.0_37的javac
编译后的TestClass.java对应的TestClass.class的二进制结构如下图所示:

澳门新葡萄京官网首页 5

下面我们就根据前面所说的Class的文件结构来解析以下上图中字节流。

1)魔数
从Class的文件结构我们知道,刚开始的4个字节是魔数,上图中从地址00000000h-00000003h的内容就是魔数,从上图可知Class的文件的魔数是0xCAFEBABE。

2)主次版本号
接下来的4个字节是主次版本号,有上图可知从00000004h-00000005h对应的是0×0000,因此Class的minor_version
为0×0000,从00000006h-00000007h对应的内容为0×0032,因此Class文件的major_version版本为
0×0032,这正好就是jdk1.6.0不带target参数编译后的Class对应的主次版本。

3)常量池的数量
接下来的2个字节从00000008h-00000009h表示常量池的数量,由上图可以知道其值为0×0018,十进制为24个,但是对于常量池的数量
需要明确一点,常量池的数量是constant_pool_count-1,为什么减一,是因为索引0表示class中的数据项不引用任何常量池中的常
量。

4)常量池
我们上面说了常量池中有不同类型的常量,下面就来看看TestClass.class的第一个常量,我们知道每个常量都有一个u1类型的tag标识来表示
常量的类型,上图中0000000ah处的内容为0x0A,转换成二级制是10,有上面的关于常量类型的描述可知tag为10的常量是Constant_Methodref_info,而Constant_Methodref_info的结够如下图所示:

澳门新葡萄京官网首页 6

其中class_index指向常量池中类型为CONSTANT_Class_info的常量,从TestClass的二进制文件结构中可以看出
class_index的值为0×0004(地址为0000000bh-0000000ch),也就是说指向第四个常量。

name_and_type_index指向常量池中类型为CONSTANT_NameAndType_info常量。从上图可以看出name_and_type_index的值为0×0013,表示指向常量池中的第19个常量。

接下来又可以通过同样的方法来找到常量池中的所有常量。不过JDK提供了一个方便的工具可以让我们查看常量池中所包含的常量。通过javap
-verbose TestClass 即可得到所有常量池中的常量,截图如下:

澳门新葡萄京官网首页 7

从上图我们可以清楚的看到,TestClass中常量池有24个常量,不要忘记了第0个常量,因为第0个常量被用来表示
Class中的数据项不引用任何常量池中的常量。从上面的分析中我们得知TestClass的第一个常量表示方法,其中class_index指向的第四
个常量为java/lang/Object,name_and_type_index指向的第19个常量值为<init>:()V,从这里可
以看出第一个表示方法的常量表示的是java编译器生成的实例构造器方法。通过同样的方法可以分析常量池的其它常量。OK,分析完常量池,我们接下来再分
析下access_flags。
5)u2 access_flags
表示类或者接口方面的访问信息,比如Class表示的是类还是接口,是否为public,static,final等。具体访问标示的含义之前已经说过
了,下面我们就来看看TestClass的访问标示。Class的访问标示是从0000010dh-0000010e,期值为0×0021,根据前面说的
各种访问标示的标志位,我们可以知道:0×0021=0×0001|0×0020 也即ACC_PUBLIC

ACC_SUPER为真,其中ACC_PUBLIC大家好理解,ACC_SUPER是jdk1.2之后编译的类都会带有的标志。

6)u2 this_class
表示类的索引值,用来表示类的全限定名称,类的索引值如下图所示:

澳门新葡萄京官网首页 8

从上图可以清楚到看到,类索引值为0×0003,对应常量池的第三个常量,通过javap的结果,我们知道第三个常量为
CONSTANT_Class_info类型的常量,通过它可以知道类的全限定名称为:com/ejushang/TestClass
/TestClass

7)u2 super_class
表示当前类的父类的索引值,索引值所指向的常量池中类型为CONSTANT_Class_info的常量,父类的索引值如下图所示,其值为0×0004,
查看常量池的第四个常量,可知TestClass的父类的全限定名称为:java/lang/Object

澳门新葡萄京官网首页 9

8)interfaces_count和
interfaces[interfaces_count]
表示接口数量以及具体的每一个接口,TestClass的接口数量以及接口如下图所示,其中
0×0001表示接口数量为1,而0×0005表示接口在常量池的索引值,找到常量池的第五个常量,其类型为CONSTANT_Class_info,其
值为:com/ejushang/TestClass/Super

澳门新葡萄京官网首页 10

9)fields_count 和 field_info,
fields_count表示类中field_info表的数量,而field_info表示类的实例变量和类变量,这里需要注意的是
field_info不包含从父类继承过来的字段,field_info的结构如下图所示:
澳门新葡萄京官网首页 11

其中access_flags表示字段的访问标示,比如public,private,protected,static,final等,access_flags的取值如下图所示:
澳门新葡萄京官网首页 12

其中name_index 和
descriptor_index都是常量池的索引值,分别表示字段的名称和字段的描述符,字段的名称容易理解,但是字段的描述符如何理解呢?其实在JVM
规范中,对于字段的描述符规定如下图所示:

澳门新葡萄京官网首页 13

其中大家需要关注一下上图最后一行,它表示的是对一维数组的描述符,对于String[][]的描述符将是[[
Ljava/lang/String,而对于int[][]的描述符为[[I。接下来的attributes_count以及
attribute_info分别表示属性表的数量以及属性表。下面我们还是以上面的TestClass为例,来看看TestClass的字段表吧。

首先我们来看一下字段的数量,TestClass的字段的数量如下图所示:

澳门新葡萄京官网首页 14

从上图中可以看出TestClass有两个字段,查看TestClass的源代码可知,确实也只有两个字段,接下来我们看看第一个字段,我们知道第一个字段应该为private
int staticVar,它在Class文件中的二进制表示如下图所示:

澳门新葡萄京官网首页 15
其中0x001A表示访问标示,通过查看access_flags表可知,其为ACC_PRIVATE,ACC_STATIC,ACC_FINAL,接下
来0×0006和0×0007分别表示常量池中第6和第7个常量,通过查看常量池可知,其值分别为:staticVar和I,其中staticVar为字
段名称,而I为字段的描述符,通过上面对描述符的解释,I所描述的是int类型的变量,接下来0×0001表示staticVar这个字段表中的属性表的
数量,从上图可以staticVar字段对应的属性表有1个,0×0008表示常量池中的第8个常量,查看常量池可以得知此属性为
ConstantValue属性,而ConstantValue属性的格式如下图所示:
澳门新葡萄京官网首页 16

其中attribute_name_index表述属性名的常量池索引,本例中为ConstantValue,而ConstantValue的
attribute_length固定长度为2,而constantValue_index表示常量池中的引用,本例中,其中为0×0009,查看第9个
常量可以知道,它表示一个类型为CONSTANT_Integer_info的常量,其值为0。

上面说完了private static final int
staticVar=0,下面我们接着说一下TestClass的private int
instanceVar=0,在本例中对instanceVar的二进制表示如下图所示:

澳门新葡萄京官网首页 17
其中0×0002表示访问标示为ACC_PRIVATE,0x000A表示字段的名称,它指向常量池中的第10个常量,查看常量池可以知道字段名称为
instanceVar,而0×0007表示字段的描述符,它指向常量池中的第7个常量,查看常量池可以知道第7个常量为I,表示类型为
instanceVar的类型为I,最后0×0000表示属性表的数量为0.

10)methods_count 和 method_info
,其中methods_count表示方法的数量,而method_info表示的方法表,其中方法表的结构如下图所示:

澳门新葡萄京官网首页 18

从上图可以看出method_info和field_info的结构是很类似的,方法表的access_flag的所有标志位以及取值如下图所示:

澳门新葡萄京官网首页 19

其中name_index和descriptor_index表示的是方法的名称和描述符,他们分别是指向常量池的索引。这里需要结解释一下方法的描述
符,方法的描述符的结构为:(参数列表)返回值,比如public int
instanceMethod(int
param)的描述符为:(I)I,表示带有一个int类型参数且返回值也为int类型的方法,接下来就是属性数量以及属性表了,方法表和字段表虽然都有
属性数量和属性表,但是他们里面所包含的属性是不同。接下来我们就以TestClass来看一下方法表的二进制表示。首先来看一下方法表数量,截图如下:

澳门新葡萄京官网首页 20
从上图可以看出方法表的数量为0×0002表示有两个方法,接下来我们来分析第一个方法,我们首先来看一下TestClass的第一个方法的access_flag,name_index,descriptor_index,截图如下:

澳门新葡萄京官网首页 21
从上图可以知道access_flags为0×0001,从上面对access_flags标志位的描述,可知方法的access_flags的取值为
ACC_PUBLIC,name_index为0x000B,查看常量池中的第11个常量,知道方法的名称为<init>,0x000C表示
descriptor_index表示常量池中的第12常量,其值为()V,表示<init>方法没有参数和返回值,其实这是编译器自动生成
的实例构造器方法。接下来的0×0001表示<init>方法的方法表有1个属性,属性截图如下:
澳门新葡萄京官网首页 22
从上图可以看出0x000D对应的常量池中的常量为Code,表示的方法的Code属性,所以到这里大家应该明白方法的那些代码是存储在Class文件方法表中的属性表中的Code属性中。接下来我们在分析一下Code属性,Code属性的结构如下图所示:
澳门新葡萄京官网首页 23

其中attribute_name_index指向常量池中值为Code的常量,attribute_length的长度表示Code属性表的长度(这里
需要注意的时候长度不包括attribute_name_index和attribute_length的6个字节的长度)。

max_stack表示最大栈深度,虚拟机在运行时根据这个值来分配栈帧中操作数的深度,而max_locals代表了局部变量表的存储空间。

max_locals的单位为slot,slot是虚拟机为局部变量分配内存的最小单元,在运行时,对于不超过32位类型的数据类型,比如
byte,char,int等占用1个slot,而double和Long这种64位的数据类型则需要分配2个slot,另外max_locals的值并
不是所有局部变量所需要的内存数量之和,因为slot是可以重用的,当局部变量超过了它的作用域以后,局部变量所占用的slot就会被重用。

code_length代表了字节码指令的数量,而code表示的时候字节码指令,从上图可以知道code的类型为u1,一个u1类型的取值为0×00-0xFF,对应的十进制为0-255,目前虚拟机规范已经定义了200多条指令。

exception_table_length以及exception_table分别代表方法对应的异常信息。

attributes_count和attribute_info分别表示了Code属性中的属性数量和属性表,从这里可以看出Class的文件结构中,属性表是很灵活的,它可以存在于Class文件,方法表,字段表以及Code属性中。

接下来我们继续以上面的例子来分析一下,从上面init方法的Code属性的截图中可以看出,属性表的长度为0×00000026,max_stack的
值为0×0002,max_locals的取值为0×0001,code_length的长度为0x0000000A,那么00000149h-
00000152h为字节码,接下来exception_table_length的长度为0×0000,而attribute_count的值为
0×0001,00000157h-00000158h的值为0x000E,它表示常量池中属性的名称,查看常量池得知第14个常量的值为
LineNumberTable,LineNumberTable用于描述java源代码的行号和字节码行号的对应关系,它不是运行时必需的属性,如果通
过-g:none的编译器参数来取消生成这项信息的话,最大的影响就是异常发生的时候,堆栈中不能显示出出错的行号,调试的时候也不能按照源代码来设置断
点,接下来我们再看一下LineNumberTable的结构如下图所示:

澳门新葡萄京官网首页 24

其中attribute_name_index上面已经提到过,表示常量池的索引,attribute_length表示属性长度,而start_pc和
line_number分表表示字节码的行号和源代码的行号。本例中LineNumberTable属性的字节流如下图所示:

澳门新葡萄京官网首页 25

上面分析完了TestClass的第一个方法,通过同样的方式我们可以分析出TestClass的第二个方法,截图如下:

澳门新葡萄京官网首页 26

其中access_flags为0×0001,name_index为0x000F,descriptor_index为0×0010,通过查看常量池可
以知道此方法为public int instanceMethod(int
param)方法。通过和上面类似的方法我们可以知道instanceMethod的Code属性为下图所示:

澳门新葡萄京官网首页 27

最后我们来分析一下,Class文件的属性,从00000191h-00000199h为Class文件中的属性表,其中0×0011表示属性的名称,查看常量池可以知道属性名称为SourceFile,我们再来看看SourceFile的结构如下图所示:

澳门新葡萄京官网首页 28

其中attribute_length为属性的长度,sourcefile_index指向常量池中值为源代码文件名称的常量,在本例中SourceFile属性截图如下:

澳门新葡萄京官网首页 29
其中attribute_length为0×00000002表示长度为2个字节,而soucefile_index的值为0×0012,查看常量池的第18个常量可以知道源代码文件的名称为TestClass.java

最后,希望对技术感兴趣的朋友多交流。个人微博:()

Class的文件结构,实例分析java
学习Java的朋友应该都知道Java从刚开始的时候就打着平台无关性的旗号,说“一次编写,到处运…

一、概述

任何一个Class文件都对应唯一一个类或接口的定义信息,但是不是所有的类或接口都得定义在文件中(它们也可以通过类加载器直接生成)。

Class文件是一组以8位字节为基础单位的二进制流,各个数据项严格按顺序排列,没有任何分隔符。Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构只有两种数据类型:无符号数和表。

无符号数:是基本数据类型,以u1、u2、u4、u8分别代表1个字节、2个字节、4个字节、8个字节的无符号数,可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成的字符串值。

:由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以“_info”结尾。整个Class文件本质上就是一张表,如下所示:

类型

名称

数量

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

fields_count

1

field_info

fields

fields_count

u2

methods_count

1

method_info

methods

methods_count

u2

attributes_count

1

attribute_info

attributes

attributes_count

 

 

接下来分别对表中的各个字段作出解释。

 

 

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];    //属性表

}

二、各个字段详解

 

使用下面的类进行说明:

[java] view
plain copy

 

  1. package com.test;  
  2.   
  3. public class Test {  
  4.     private int m;  
  5.       
  6.     public int getM(){  
  7.         return m + 1;  
  8.     }  
  9. }  

编译后的class文件如下:

 

澳门新葡萄京官网首页 30

 

1. 通过实例来看

public interface InterA {

    void interA();
} 
public interface InterB {
    String interB(int i);
}
public interface InterC {
    void interC();
}
public class Base implements InterA {

    private int baseInt;
    protected String baseString;

    public int getBaseInt() {
        return baseInt;
    }
    public void setBaseInt(int baseInt) {
        this.baseInt = baseInt;
    }

    @Override
    public void interA() {
        System.out.println("the interA in Base");
    }
} 

 public class Sub extends Base implements InterB, InterC {

    private int subInt;
    private static String subString;
    private static Object subObject;

    public int getSubInt() {
        return subInt;
    }
    public void setSubInt(int subInt) {
        this.subInt = subInt;
    }
    public static String getSubString() {
        return subString;
    }
    public static void setSubString(String subString) {
        Sub.subString = subString;
    }
    public static Object getSubObject() {
        return subObject;
    }
    public static void setSubObject(Object subObject) {
        Sub.subObject = subObject;
    }

    @Override
    public void interC() {
        System.out.println("the interC in Sub");
    }

    @Override
    public String interB(int i) {
        return "the interB in Sub";
    }
}

我们使用WinHex查看Sub类的.class文件:

澳门新葡萄京官网首页 31

1.魔数

每个class文件的头4个字节称为魔数,它唯一的作用是确定这个文件是否为一个能被虚拟机接受的Class文件。很多文件存储标准中都使用魔数来进行身份识别,譬如图片格式gif、jpeg等。使用魔数而不是拓展名来进行识别主要是基于安全方面的考虑,因为文件拓展格式可以随意改动。

 

Class文件的魔数为:0xCAFEBABE(咖啡宝贝?),这个魔数似乎也预示着日后JAVA这个商标名称的出现。

 

澳门新葡萄京官网首页 32

 

2. 魔数

作用:确定该文件是否是虚拟机可接受的class文件。java的魔数统一为
0xCAFEBABE (来源于一款咖啡)。

区域:文件第0~3字节。

2.版本号

第五六个字节是次版本号(Minor Version),第7和第8个字节是主版本号(Major
Version)。

高版本的JDK可以向下兼容以前版本的Class文件,但是无法运行以后版本的Class文件,即使文件格式并未发生变化,虚拟机也必须拒绝执行超过其版本号的Class文件。

 

3. 版本号

作用:表示class文件的版本,由minorversion和majorversion组成。

区域:文件第4~7字节。

澳门新葡萄京官网首页 33

51代表,jdk为1.7.0

需要注意的是java版本号是从45开始的,大版本发布,主版本号+1.高版本的jdk能向下兼容以前版本的class文件,但不兼容以后版本的class文件。

3.常量池

澳门新葡萄京官网首页 34

 

常量池可以理解为Class文件之中的资源仓库,是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,同时也是在Class文件中第一个出现的表类型数据项目。

由于常量池中常量的数目是不固定的所以在常量池入口需要放置一个2字节长的无符号数constatn_pool_count来代表常量池容量计数值。这个容量计数从1而不是0开始。

 

constant_pool_count:占2字节,0x0016,转化为十进制为22,即说明常量池中有21个常量(只有常量池的计数是从1开始的,其它集合类型均从0开始),索引值为1~22。第0项常量具有特殊意义,如果某些指向常量池索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,这种情况可以将索引值置为0来表示

 

常量池中主要存放两大类常量:字面量和符号引用。字面量如文本字符串、声明为final的常量值等。符号引用包括三类常量:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。

 

常量池中的每一项常量都是一个表,在JDK1.7之前共有11种结构各不相同的表数据结构。这些表数据结构在表开始的第一位是一个u1类型的标志位,代表当前这个常量属于那种常量类型。如下表所示:

类型

简介

项目

类型

描述

CONSTANT_Utf8_info

utf-8缩略编码字符串

tag

u1

值为1

length

u2

utf-8缩略编码字符串占用字节数

bytes

u1

长度为length的utf-8缩略编码字符串

CONSTANT_Integer_info

整形字面量

tag

u1

值为3

bytes

u4

按照高位在前储存的int值

CONSTANT_Float_info

浮点型字面量

tag

u1

值为4

bytes

u4

按照高位在前储存的float值

CONSTANT_Long_info

长整型字面量

tag

u1

值为5

bytes

u8

按照高位在前储存的long值

CONSTANT_Double_info

双精度浮点型字面量

tag

u1

值为6

bytes

u8

按照高位在前储存的double值

CONSTANT_Class_info

类或接口的符号引用

tag

u1

值为7

 

index

u2

指向全限定名常量项的索引

CONSTANT_String_info

字符串类型字面量

tag

u1

值为8

index

u2

指向字符串字面量的索引

CONSTANT_Fieldref_info

字段的符号引用

tag

u1

值为9

index

u2

指向声明字段的类或接口描述符CONSTANT_Class_info的索引项

index

u2

指向字段描述符CONSTANT_NameAndType_info的索引项

CONSTANT_Methodref_info

类中方法的符号引用

tag

u1

值为10

index

u2

指向声明方法的类描述符CONSTANT_Class_info的索引项

index

u2

指向名称及类型描述符CONSTANT_NameAndType_info的索引项

CONSTANT_InterfaceMethodref_info

接口中方法的符号引用

tag

u1

值为11

index

u2

指向声明方法的接口描述符CONSTANT_Class_info的索引项

index

u2

指向名称及类型描述符CONSTANT_NameAndType_info的索引项

CONSTANT_NameAndType_info

字段或方法的部分符号引用

tag

u1

值为12

index

u2

指向该字段或方法名称常量项的索引

index

u2

指向该字段或方法描述符常量项的索引

 

首先来看常量池中的第一项常量,其标志位为0x07,是一个CONSTANT_Class_info类型常量,此类型常量代表一个类或接口的符号引用。根据其数据结构,接下来2位字节用来保存一个索引值,它指向常量池中一个CONSTANT_Utf8_info类型的常量,此常量代表了这个类或接口的全限定名,索引值为0x0002,即指向了常量池中的第二项常量。

第二项常量标志位为0x01,确实是一个CONSTANT_Utf8_info类型的常量。根据其数据结构,接下来2个字节用来保存utf-8缩略编码字符串长度,其值为0x000D,转化为十进制为13,即接下来的13个字节为一个utf-8缩略编码的字符串,为com/test/Test,可以看到正好是测试类的全限定名。

 

4. 常量池

常量池的大小是不固定的,根据你的类中的常量的多少而定,所以在常量池的入口,放置了一个u2类型的表示常量池中常量个数的常量池容量计数器。计数器从1开始,第0位有特殊含义,表示指向常量池的索引值数据不引用任何一个常量池项目。池中的数据项就像数组一样是通过索引访问的。

澳门新葡萄京官网首页 35

我们可以清楚的看到,我们常量池中有63-1=62个常量。这些常量是什么呢?

要存放字面量Literal和符号引用Symbolic References。

字面量可能是文本字符串,或final的常量值。

符号引用包括以下:

  • 类或接口全限定名 Full Qualified Name
  • 字段名称和描述符 Descriptor
  • 方法名称和描述符

我们使用反编译工具查看一下:

E:programJVMbincomgisskyclazz>javap -v Sub.class
Classfile /E:/program/JVM/bin/com/gissky/clazz/Sub.class
  Last modified 2015-2-22; size 1363 bytes
  MD5 checksum 2dc77c79e4790422407eb7092085883c
  Compiled from "Sub.java"
public class com.gissky.clazz.Sub extends com.gissky.clazz.Base implements com.gissky.clazz.InterB,com.gissky.clazz.InterC
  SourceFile: "Sub.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             //  com/gissky/clazz/Sub    →类和接口的全限定名    
   #2 = Utf8               com/gissky/clazz/Sub
   #3 = Class              #4             //  com/gissky/clazz/Base
   #4 = Utf8               com/gissky/clazz/Base
   #5 = Class              #6             //  com/gissky/clazz/InterB
   #6 = Utf8               com/gissky/clazz/InterB
   #7 = Class              #8             //  com/gissky/clazz/InterC
   #8 = Utf8               com/gissky/clazz/InterC
   #9 = Utf8               subInt 
  #10 = Utf8              I 
  #11 = Utf8              subString
  #12 = Utf8               Ljava/lang/String;
  #13 = Utf8               subObject
  #14 = Utf8               Ljava/lang/Object;
  #15 = Utf8               <init>
  #16 = Utf8               ()V
  #17 = Utf8               Code
  #18 = Methodref          #3.#19         //  com/gissky/clazz/Base."<init>":()V
  #19 = NameAndType        #15:#16        //  "<init>":()V
  #20 = Utf8               LineNumberTable
  #21 = Utf8               LocalVariableTable
  #22 = Utf8               this
  #23 = Utf8               Lcom/gissky/clazz/Sub;
  #24 = Utf8               getSubInt
  #25 = Utf8               ()I 
  #26 = Fieldref           #1.#27         //  com/gissky/clazz/Sub.subInt:I         → 类中字段的符号引用
  #27 = NameAndType        #9:#10         //  subInt:I                                           → 类中字段的部分符号引用之名称和类型
  #28 = Utf8               setSubInt
  #29 = Utf8               (I)V
  #30 = Utf8               getSubString
  #31 = Utf8               ()Ljava/lang/String;
  #32 = Fieldref           #1.#33         //  com/gissky/clazz/Sub.subString:Ljava/lang/String;
  #33 = NameAndType        #11:#12        //  subString:Ljava/lang/String;
  #34 = Utf8               setSubString
  #35 = Utf8               (Ljava/lang/String;)V
  #36 = Utf8               getSubObject
  #37 = Utf8               ()Ljava/lang/Object;
  #38 = Fieldref           #1.#39         //  com/gissky/clazz/Sub.subObject:Ljava/lang/Object;
  #39 = NameAndType        #13:#14        //  subObject:Ljava/lang/Object;
  #40 = Utf8               setSubObject
  #41 = Utf8               (Ljava/lang/Object;)V
  #42 = Utf8               interC
  #43 = Fieldref           #44.#46        //  java/lang/System.out:Ljava/io/PrintStream;
  #44 = Class              #45            //  java/lang/System
  #45 = Utf8               java/lang/System
  #46 = NameAndType        #47:#48        //  out:Ljava/io/PrintStream;
  #47 = Utf8               out
  #48 = Utf8               Ljava/io/PrintStream;
  #49 = String             #50            //  the interC in Sub
  #50 = Utf8               the interC in Sub
  #51 = Methodref          #52.#54        //  java/io/PrintStream.println:(Ljava/lang/String;)V
  #52 = Class              #53            //  java/io/PrintStream
  #53 = Utf8               java/io/PrintStream
  #54 = NameAndType        #55:#35        //  println:(Ljava/lang/String;)V
  #55 = Utf8               println
  #56 = Utf8               interB
  #57 = Utf8               (I)Ljava/lang/String;
  #58 = String             #59            //  the interB in Sub                                    →方法中用到的String常量
  #59 = Utf8               the interB in Sub
  #60 = Utf8               i
  #61 = Utf8               SourceFile
  #62 = Utf8               Sub.java

常量池中的项目类型如下:

  • CONSTANT_Utf8_info tag标志位为1, UTF-8编码的字符串
  • CONSTANT_Integer_info tag标志位为3, 整形字面量
  • CONSTANT_Float_info tag标志位为4, 浮点型字面量
  • CONSTANT_Long_info tag标志位为5, 长整形字面量
  • CONSTANT_Double_info tag标志位为6, 双精度字面量
  • CONSTANT_Class_info tag标志位为7, 类或接口的符号引用
  • CONSTANT_String_info tag标志位为8,字符串类型的字面量
  • CONSTANT_Fieldref_info tag标志位为9, 字段的符号引用
  • CONSTANT_Methodref_info tag标志位为10,类中方法的符号引用
  • CONSTANT_InterfaceMethodref_info tag标志位为11,
    接口中方法的符号引用
  • CONSTANT_NameAndType_info tag
    标志位为12,字段和方法的名称以及类型的符号引用

4.访问标志

 

澳门新葡萄京官网首页 36

 

在常量池结束之后,紧接着的两个字节代表访问标志,用于识别一些类或者接口层次的访问信息。如下表所示。

 

志名称

标志值

含义

ACC_PUBLIC

0x0001

是否为public类型

ACC_FINAL

0x0010

是否被声明为final,只有类可设置

ACC_SUPER

0x0020

是否允许使用invokespecial字节码指令,JDK1.2以后编译出来的类这个标志为真

ACC_INTERFACE

0x0200

标识这是一个接口

ACC_ABSTRACT

0x0400

是否为abstract类型,对于接口和抽象类,此标志为真,其它类为假

ACC_SYNTHETIC

0x1000

标识别这个类并非由用户代码产生

ACC_ANNOTATION

0x2000

标识这是一个注解

ACC_ENUM

0x4000

标识这是一个枚举

 

根据上面的表格,测试类的访问标志0x0021= 0x0001 | 0x0020 =ACC_PUBLIC |
ACC_SUPER 

 

5. 类或接口访问标志

表示类或者接口方面的访问信息,比如Class表示的是类还是接口,是否为public、static、final等。,下面我们就来看看TestClass的访问标示。Class的访问标志值为0×0021:

澳门新葡萄京官网首页 37

根据前面说的各种访问标示的标志位,我们可以知道:0×0021=0×0001|0×0020
也即ACC_PUBLIC 和
ACC_SUPER为真,其中ACC_PUBLIC大家好理解,ACC_SUPER是jdk1.2之后编译的类都会带有的标志。

澳门新葡萄京官网首页 38

5.类索引、父类索引和接口索引集合

 

澳门新葡萄京官网首页 39

 

Class文件中由这3项数据来确定这个类的继承关系

this_class:类索引,用于确定这个类的全限定名,占2字节

super_class:父类索引,用于确定这个类父类的全限定名(Java语言不允许多重继承,故父类索引只有一个。除了java.lang.Object类之外所有类都有父类,故除了java.lang.Object类之外,所有类该字段值都不为0),占2字节

interfaces_count:接口索引计数器,占2字节。如果该类没有实现任何接口,则该计数器值为0,并且后面的接口的索引集合将不占用任何字节,

interfaces:接口索引集合,一组u2类型数据的集合。用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句(如果该类本身为接口,则为extends语句)后的接口顺序从左至右排列在接口的索引集合中

this_class、super_class与interfaces中保存的索引值均指向常量池中一个CONSTANT_Class_info类型的常量,通过这个常量中保存的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串

this_class的值为0x0001,即常量池中第一个常量,super_class的值为0x0003,即常量池中的第三个常量,interfaces_counts的值为0x0000,故接口索引集合大小为0

6. 类索引、父类索引与接口索引集合

Class文件中由这3项数据来确定类的继承关系。

类索引和父类索引都是指向常量池中的常量索引:

澳门新葡萄京官网首页 40

紧接着后面是一个接口的计数器和接口描述符:

澳门新葡萄京官网首页 41

6.字段表集合

字段表用于描述接口或者类中声明的变量,包括类级变量和实例级变量(是否是static),但不包括在方法内部声明的局部变量。

 

澳门新葡萄京官网首页 42

fields_count:字段表计数器,即字段表集合中的字段表数据个数。占2字节,其值为0x0001,即只有一个字段表数据,也就是测试类中只包含一个变量(不算方法内部变量)

fields:字段表集合,一组字段表类型数据的集合。字段表用于描述接口或类中声明的变量,包括类级别(static)和实例级别变量,不包括在方法内部声明的变量

在Java中一般通过如下几项描述一个字段:字段作用域(public、protected、private修饰符)、是类级别变量还是实例级别变量(static修饰符)、可变性(final修饰符)、并发可见性(volatile修饰符)、可序列化与否(transient修饰符)、字段数据类型(基本类型、对象、数组)以及字段名称。在字段表中,变量修饰符使用标志位表示,字段数据类型和字段名称则引用常量池中常量表示,字段表格式如下表所示:

类型

名称

数量

u2

access_flags

1

u2

name_index

1

u2

descriptor_index

1

u2

attributes_count

1

attribute_info

attributes

attributes_count

 

字段修饰符放在access_flags中,占2字节,其值为0x0002,可见这个字段由private修饰,与访问标志位十分相似

标志名称

标志值

含义

ACC_PUBLIC

0x0001

字段是否为public

ACC_PRIVATE

0x0002

字段是否为private

ACC_PROTECTED

0x0004

字段是否为protected

ACC_STATIC

0x0008

字段是否为static

ACC_FINAL

0x0010

字段是否为final

ACC_VOLATILE

0x0040

字段是否为volatile

ACC_TRANSIENT

0x0080

字段是否为transient

ACC_SYNTHETIC

0x1000

字段是否为编译器自动产生

ACC_ENUM

0x4000

字段是否为enum

7. 字段表集合

作用:描述接口或者类中声明的类变量以及实例变量,不包括方法中的局部变量。

紧接着接口索引集合之后的2字节是字段计数器:

澳门新葡萄京官网首页 43

表示我们类中有3个字段,这里便是subInt、subString、subObject
3个字段。紧接其后的是字段表,字段表结构为:

field_info
{ 
    u2                               access_flags; 
    u2                               name_index; 
    u2                               descriptor_index; 
    u2                               attributes_count; 
    attribute_info          attributes[attributes_count]; 
}

澳门新葡萄京官网首页 44

access_flags项的值是用于定义字段被访问权限和基础属性的掩码标志。取值范围如下表:

澳门新葡萄京官网首页 45

描述符标识字符含义:

澳门新葡萄京官网首页 46

V 表示特殊类型void。

对于数组类型,每一个维度将使用一个前置的”[“字符来描述,如一个定义的”java.lang.String[][]“类型的二维数组,将被记录为:”[[Ljava/lang/String;”,一个整型数组”int[]“将被记录为”[I”

父类中的字段不会出现在子类的字段表中。

7.方法表集合

澳门新葡萄京官网首页 47

methods_count:方法表计数器,即方法表集合中的方法表数据个数。占2字节,其值为0x0002,即测试类中有2个方法(还自动增加了一个构造函数)

methods:方法表集合,一组方法表类型数据的集合。方法表结构和字段表结构一样:

类型

名称

数量

u2

access_flags

1

u2

name_index

1

u2

descriptor_index

1

u2

attributes_count

1

attribute_info

attributes

attributes_count

数据项的含义非常相似,仅在访问标志位和属性表集合中的可选项上有略微不同

由于ACC_VOLATILE标志和ACC_TRANSIENT标志不能修饰方法,所以access_flags中不包含这两项,同时增加ACC_SYNCHRONIZED标志、ACC_NATIVE标志、ACC_STRICTFP标志和ACC_ABSTRACT标志

标志名称

标志值

含义

ACC_PUBLIC

0x0001

字段是否为public

ACC_PRIVATE

0x0002

字段是否为private

ACC_PROTECTED

0x0004

字段是否为protected

ACC_STATIC

0x0008

字段是否为static

ACC_FINAL

0x0010

字段是否为final

ACC_SYNCHRONIZED

0x0020

字段是否为synchronized

ACC_BRIDGE

0x0040

方法是否是由编译器产生的桥接方法

ACC_VARARGS

0x0080

方法是否接受不定参数

ACC_NATIVE

0x0100

字段是否为native

ACC_ABSTRACT

0x0400

字段是否为abstract

ACC_STRICTFP

0x0800

字段是否为strictfp

ACC_SYNTHETIC

0x1000

字段是否为编译器自动产生

 

第一个方法(由编译器自动添加的默认构造方法):

澳门新葡萄京官网首页 48

access_flags为0x0001,即public;name_index为0x0007,即常量池中第7个常量;descriptor_index为0x0008,即常量池中第8个常量

[plain] view
plaincopy澳门新葡萄京官网首页 49澳门新葡萄京官网首页 50

 

 

  1. const #7 = Asciz        <init>;  
  2. const #8 = Asciz        ()V;  

澳门新葡萄京官网首页 51

接下来2个字节为属性计数器,其值为0x0001,说明这个方法的属性表集合中有一个属性,属性名称为接下来2位0x0009,指向常量池中第9个常量:Code。接下来4位为0x0000002F,表示Code属性值的字节长度为47。接下来2位为0x0001,表示该方法的操作数栈的深度最大值为1。接下来2位依然为0x0001,表示该方法的局部变量占用空间为1。接下来4位为0x0000005,则紧接着的5个字节0x2AB7000AB1为该方法编译后生成的字节码指令(各字节对应的指令不介绍了,可查询虚拟机字节码指令表)。接下来2个字节为0x0000,说明Code属性异常表集合为空。

澳门新葡萄京官网首页 52

接下来2个字节为0x0002,说明Code属性带有2个属性,那么接下来2位0x000C即为Code属性第一个属性的属性名称,指向常量池中第12个常量:LineNumberTable。接下来4位为0x00000006,表示LineNumberTable属性值所占字节长度为6。接下来2位为0x0001,即该line_number_table中只有一个line_number_info表,start_pc为0x0000,line_number为0x0003,LineNumberTable属性结束。

澳门新葡萄京官网首页 53

接下来2位0x000D为Code属性第二个属性的属性名,指向常量池中第13个常量:LocalVariableTable。该属性值所占的字节长度为0x0000000C=12。接下来2位为0x0001,说明local_variable_table中只有一个local_variable_info表,按照local_variable_info表结构,start_pc为0x0000,length为0x0005,name_index为0x000E,指向常量池中第14个常量:this,descriptor_index为0x000F,指向常量池中第15个常量:Lcom/test/Test;,index为0x0000。第一个方法结束

第二个方法:

澳门新葡萄京官网首页 54

access_flags为0x0001,即public;name_index为0x0010,即常量池中第16个常量;descriptor_index为0x0011,即常量池中第17个常量

[plain] view
plaincopy澳门新葡萄京官网首页 55澳门新葡萄京官网首页 56

 

 

  1. const #16 = Asciz       getM;  
  2. const #17 = Asciz       ()I;  

澳门新葡萄京官网首页 57

接下来2个字节为属性计数器,其值为0x0001,说明这个方法有一个方法属性,属性名称为接下来2位0x0009,指向常量池中第9个常量:Code。接下来4位为0x00000031,表示Code属性值的字节长度为49。接下来2位为0x0002,表示该方法的操作数栈的深度最大值为2。接下来2位为0x0001,表示该方法的局部变量占用空间为1。接下来4位为0x0000007,则紧接着的7个字节0x2AB400120460AC为该方法编译后生成的字节码指令。接下来2个字节为0x0000,说明Code属性异常表集合为空。

澳门新葡萄京官网首页 58

接下来2个字节为0x0002,说明Code属性带有2个属性,那么接下来2位0x000C即为Code属性第一个属性的属性名称,指向常量池中第12个常量:LineNumberTable。接下来4位为0x00000006,表示LineNumberTable属性值所占字节长度为6。接下来2位为0x0001,即该line_number_table中只有一个line_number_info表,start_pc为0x0000,line_number为0x0007,LineNumberTable属性结束。

澳门新葡萄京官网首页 59

和第一个方法的LocalVariableTable属性基本相同,唯一的区别是局部变量this的作用范围覆盖的长度为7而不是5,第二个方法结束

如果子类没有重写父类的方法,方法表集合中就不会出现父类方法的信息;有可能会出现由编译器自动添加的方法(如:<init>,实例类构造器)

在Java语言中,重载一个方法除了要求和原方法拥有相同的简单名称外,还要求必须拥有一个与原方法不同的特征签名(方法参数集合),由于特征签名不包含返回值,故Java语言中不能仅仅依靠返回值的不同对一个已有的方法重载;但是在Class文件格式中,特征签名即为方法描述符,只要是描述符不完全相同的2个方法也可以合法共存,即2个除了返回值不同之外完全相同的方法在Class文件中也可以合法共存

javap工具在后半部分会列出分析完成的方法(可以看到和我们的分析结果是一样的):

[plain] view
plaincopy澳门新葡萄京官网首页 60澳门新葡萄京官网首页 61

 

 

  1. d:>javap -verbose Test  
  2. ……  
  3. {  
  4. public com.test.Test();  
  5.   Code:  
  6.    Stack=1, Locals=1, Args_size=1  
  7.    0:   aload_0  
  8.    1:   invokespecial   #10; //Method java/lang/Object.”<init>”:()V  
  9.    4:   return  
  10.   LineNumberTable:  
  11.    line 3: 0  
  12.   
  13.   LocalVariableTable:  
  14.    Start  Length  Slot  Name   Signature  
  15.    0      5      0    this       Lcom/test/Test;  
  16.   
  17. public int getM();  
  18.   Code:  
  19.    Stack=2, Locals=1, Args_size=1  
  20.    0:   aload_0  
  21.    1:   getfield        #18; //Field m:I  
  22.    4:   iconst_1  
  23.    5:   iadd  
  24.    6:   ireturn  
  25.   LineNumberTable:  
  26.    line 7: 0  
  27.   
  28.   LocalVariableTable:  
  29.    Start  Length  Slot  Name   Signature  
  30.    0      7      0    this       Lcom/test/Test;  
  31. }  

 

8. 方法表集合

字段表集合结束后便是方法表集合。

作用:描述该类中的方法。

和字段表一样,使用一个u2类型的方法计数器,记录该类中方法的个数。

澳门新葡萄京官网首页 62

表示我们的类中有9个方法。

方法表的结构如下图所示

澳门新葡萄京官网首页 63

其中name_index和descriptor_index表示的是方法的名称和描述符,他们分别是指向常量池的索引。这里需要结解释一下方法的描述符,方法的描述符的结构为:(参数列表)返回值,比如public
int instanceMethod(int
param)的描述符为:(I)I,表示带有一个int类型参数且返回值也为int类型的方法,方法java.lang.String.toString()的描述符为”()Ljava/lang/String;”,int
IndexOf(char[] source,int sourceOffset,int sourceCount,char[] target
int targetOffset,int targetCount,int fromIndex)
表示为([CII[CII)I。接下来就是属性数量以及属性表了,方法表和字段表虽然都有
属性数量和属性表,但是他们里面所包含的属性是不同。

澳门新葡萄京官网首页 64

如果父类方法在子类中没有被重写(@Override),方法表中就不会出现来自父类的方法信息。

8.属性表集合

在Class文件、属性表、方法表中都可以包含自己的属性表集合,用于描述某些场景的专有信息

与Class文件中其它数据项对长度、顺序、格式的严格要求不同,属性表集合不要求其中包含的属性表具有严格的顺序,并且只要属性的名称不与已有的属性名称重复,任何人实现的编译器可以向属性表中写入自己定义的属性信息。虚拟机在运行时会忽略不能识别的属性,为了能正确解析Class文件,虚拟机规范中预定义了虚拟机实现必须能够识别的9项属性:

属性名称

使用位置

含义

Code

方法表

Java代码编译成的字节码指令

ConstantValue

字段表

final关键字定义的常量值

Deprecated

类文件、字段表、方法表

被声明为deprecated的方法和字段

Exceptions

方法表

方法抛出的异常

InnerClasses

类文件

内部类列表

LineNumberTale

Code属性

Java源码的行号与字节码指令的对应关系

LocalVariableTable

Code属性

方法的局部变量描述

SourceFile

类文件

源文件名称

Synthetic

类文件、方法表、字段表

标识方法或字段是由编译器自动生成的

每种属性均有各自的表结构。这9种表结构有一个共同的特点,即均由一个u2类型的属性名称开始,可以通过这个属性名称来判段属性的类型

Code属性:Java程序方法体中的代码经过Javac编译器处理后,最终变为字节码指令存储在Code属性中。当然不是所有的方法都必须有这个属性(接口中的方法或抽象方法就不存在Code属性),Code属性表结构如下:

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

max_stack

1

u2

max_locals

1

u4

code_length

1

u1

code

code_length

u2

exception_table_length

1

exception_info

exception_table

exception_table_length

u2

attributes_count

1

attribute_info

attributes

attributes_count

 

max_stack:操作数栈深度最大值,在方法执行的任何时刻,操作数栈深度都不会超过这个值。虚拟机运行时根据这个值来分配栈帧的操作数栈深度

max_locals:局部变量表所需存储空间,单位为Slot(参见备注四)。并不是所有局部变量占用的Slot之和,当一个局部变量的生命周期结束后,其所占用的Slot将分配给其它依然存活的局部变量使用,按此方式计算出方法运行时局部变量表所需的存储空间

code_length和code:用来存放Java源程序编译后生成的字节码指令。code_length代表字节码长度,code是用于存储字节码指令的一系列字节流。

每一个指令是一个u1类型的单字节,当虚拟机读到code中的一个字节码(一个字节能表示256种指令,Java虚拟机规范定义了其中约200个编码对应的指令),就可以判断出该字节码代表的指令,指令后面是否带有参数,参数该如何解释,虽然code_length占4个字节,但是Java虚拟机规范中限制一个方法不能超过65535条字节码指令,如果超过,Javac将拒绝编译

ConstantValue属性:通知虚拟机自动为静态变量赋值,只有被static关键字修饰的变量(类变量)才可以使用这项属性。其结构如下:

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

constantvalue_index

1

可以看出ConstantValue属性是一个定长属性,其中attribute_length的值固定为0x00000002,constantvalue_index为一常量池字面量类型常量索引(Class文件格式的常量类型中只有与基本类型和字符串类型相对应的字面量常量,所以ConstantValue属性只支持基本类型和字符串类型)

对非static类型变量(实例变量,如:int a =
123;)的赋值是在实例构造器<init>方法中进行的

对类变量(如:static int a =
123;)的赋值有2种选择,在类构造器<clinit>方法中或使用ConstantValue属性。当前Javac编译器的选择是:如果变量同时被static和final修饰(虚拟机规范只要求有ConstantValue属性的字段必须设置ACC_STATIC标志,对final关键字的要求是Javac编译器自己加入的要求),并且该变量的数据类型为基本类型或字符串类型,就生成ConstantValue属性进行初始化;否则在类构造器<clinit>方法中进行初始化

Exceptions属性:列举出方法中可能抛出的受查异常(即方法描述时throws关键字后列出的异常),与Code属性平级,与Code属性包含的异常表不同,其结构为:

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

number_of_exceptions

1

u2

exception_index_table

number_of_exceptions

 

 

 

number_of_exceptions表示可能抛出number_of_exceptions种受查异常

exception_index_table为异常索引集合,一组u2类型exception_index的集合,每一个exception_index为一个指向常量池中一CONSTANT_Class_info型常量的索引,代表该受查异常的类型

InnerClasses属性:该属性用于记录内部类和宿主类之间的关系。如果一个类中定义了内部类,编译器将会为这个类与这个类包含的内部类生成InnerClasses属性,结构为:

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

number_of_classes

1

inner_classes_info

inner_classes

number_of_classes

inner_classes为内部类表集合,一组内部类表类型数据的集合,number_of_classes即为集合中内部类表类型数据的个数

每一个内部类的信息都由一个inner_classes_info表来描述,inner_classes_info表结构如下:

类型

名称

数量

u2

inner_class_info_index

1

u2

outer_class_info_index

1

u2

inner_name_index

1

u2

inner_name_access_flags

1

inner_class_info_index和outer_class_info_index指向常量池中CONSTANT_Class_info类型常量索引,该CONSTANT_Class_info类型常量指向常量池中CONSTANT_Utf8_info类型常量,分别为内部类的全限定名和宿主类的全限定名

inner_name_index指向常量池中CONSTANT_Utf8_info类型常量的索引,为内部类名称,如果为匿名内部类,则该值为0

inner_name_access_flags类似于access_flags,是内部类的访问标志

标志名称

标志值

含义

ACC_PUBLIC

0x0001

内部类是否为public

ACC_PRIVATE

0x0002

内部类是否为private

ACC_PROTECTED

0x0004

内部类是否为protected

ACC_STATIC

0x0008

内部类是否为static

ACC_FINAL

0x0010

内部类是否为final

ACC_INTERFACE

0x0020

内部类是否为一个接口

ACC_ABSTRACT

0x0400

内部类是否为abstract

ACC_SYNTHETIC

0x1000

内部类是否为编译器自动产生

ACC_ANNOTATION

0x4000

内部类是否是一个注解

ACC_ENUM

0x4000

内部类是否是一个枚举

LineNumberTale属性:用于描述Java源码的行号与字节码行号之间的对应关系,非运行时必需属性,会默认生成至Class文件中,可以使用Javac的-g:none或-g:lines关闭或要求生成该项属性信息,其结构如下:

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

line_number_table_length

1

line_number_info

line_number_table

line_number_table_length

line_number_table是一组line_number_info类型数据的集合,其所包含的line_number_info类型数据的数量为line_number_table_length,line_number_info结构如下:

类型

名称

数量

说明

u2

start_pc

1

字节码行号

u2

line_number

1

Java源码行号

不生成该属性的最大影响是:1,抛出异常时,堆栈将不会显示出错的行号;2,调试程序时无法按照源码设置断点

LocalVariableTable属性:用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系,非运行时必需属性,默认不会生成至Class文件中,可以使用Javac的-g:none或-g:vars关闭或要求生成该项属性信息,其结构如下:

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

local_variable_table_length

1

local_variable_info

local_variable_table

local_variable_table_length

local_variable_table是一组local_variable_info类型数据的集合,其所包含的local_variable_info类型数据的数量为local_variable_table_length,local_variable_info结构如下:

类型

名称

数量

说明

u2

start_pc

1

局部变量的生命周期开始的字节码偏移量

u2

length

1

局部变量作用范围覆盖的长度

u2

name_index

1

指向常量池中CONSTANT_Utf8_info类型常量的索引,局部变量名称

u2

descriptor_index

1

指向常量池中CONSTANT_Utf8_info类型常量的索引,局部变量描述符

u2

index

1

局部变量在栈帧局部变量表中Slot的位置,如果这个变量的数据类型为64位类型(long或double),

它占用的Slot为index和index+1这2个位置

start_pc + length即为该局部变量在字节码中的作用域范围

不生成该属性的最大影响是:1,当其他人引用这个方法时,所有的参数名称都将丢失,IDE可能会使用诸如arg0、arg1之类的占位符代替原有的参数名称,对代码运行无影响,会给代码的编写带来不便;2,调试时调试器无法根据参数名称从运行上下文中获取参数值

SourceFile属性:用于记录生成这个Class文件的源码文件名称,为可选项,可以使用Javac的-g:none或-g:source关闭或要求生成该项属性信息,其结构如下:

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

sourcefile_index

1

可以看出SourceFile属性是一个定长属性,sourcefile_index是指向常量池中一CONSTANT_Utf8_info类型常量的索引,常量的值为源码文件的文件名

对大多数文件,类名和文件名是一致的,少数特殊类除外(如:内部类),此时如果不生成这项属性,当抛出异常时,堆栈中将不会显示出错误代码所属的文件名

Deprecated属性和Synthetic属性:这两个属性都属于标志类型的布尔属性,只存在有和没有的区别,没有属性值的概念

Deprecated属性表示某个类、字段或方法已经被程序作者定为不再推荐使用,可在代码中使用@Deprecated注解进行设置

Synthetic属性表示该字段或方法不是由Java源码直接产生的,而是由编译器自行添加的(当然也可设置访问标志中的ACC_SYNTHETIC标志,所有由非用户代码产生的类、方法和字段都应当至少设置Synthetic属性和ACC_SYNTHETIC标志位中的一项,唯一的例外是实例构造器<init>和类构造器<clinit>方法)

这两项属性的结构为(当然attribute_length的值必须为0x00000000):

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

澳门新葡萄京官网首页 65

起始2位为0x0001,说明有一个类属性。接下来2位为属性的名称,0x0014,指向常量池中第20个常量:SourceFile。接下来4位为0x00000002,说明属性体长度为2字节。最后2个字节为0x0014,指向常量池中第21个常量:Test.java,即这个Class文件的源码文件名为Test.java

 

PS:

1,全限定名:将类全名中的“.”替换为“/”,为了保证多个连续的全限定名之间不产生混淆,在最后加上“;”表示全限定名结束。例如:”com.test.Test”类的全限定名为”com/test/Test;”

2,简单名称:没有类型和参数修饰的方法或字段名称。例如:”public void
add(int a,int b){…}”该方法的简单名称为”add”,”int a =
123;”该字段的简单名称为”a”

3,描述符:描述字段的数据类型、方法的参数列表(包括数量、类型和顺序)和返回值。根据描述符规则,基本数据类型和代表无返回值的void类型都用一个大写字符表示,而对象类型则用字符L加对象全限定名表示

标识字符

含义

B

基本类型byte

C

基本类型char

D

基本类型double

F

基本类型float

I

基本类型int

J

基本类型long

S

基本类型short

Z

基本类型boolean

V

特殊类型void

L

对象类型,如:Ljava/lang/Object;

对于数组类型,每一维将使用一个前置的“[”字符来描述,如:”int[]”将被记录为”[I”,”String[][]”将被记录为”[[Ljava/lang/String;”

用描述符描述方法时,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组”()”之内,如:方法”String
getAll(int id,String
name)”的描述符为”(I,Ljava/lang/String;)Ljava/lang/String;”

4,Slot,虚拟机为局部变量分配内存所使用的最小单位,长度不超过32位的数据类型占用1个Slot,64位的数据类型(long和double)占用2个Slot

9. 属性表集合

上面的方法表中我们就看到<init>方法有一个Code的属性。在本节我们将阐述这些属性:

Code属性:

该属性里主要存放由javac编译器处理后得到的字节码指令。

澳门新葡萄京官网首页 66

其中attribute_name_index指向常量池中值为Code的常量,attribute_length的长度表示Code属性表的长度(这里
需要注意的时候长度不包括attribute_name_index和attribute_length的6个字节的长度)。

max_stack表示最大栈深度,虚拟机在运行时根据这个值来分配栈帧中操作数的深度,而max_locals代表了局部变量表所需的存储空间。

max_locals的单位为slot,slot是虚拟机为局部变量分配内存的最小单元,在运行时,对于不超过32位类型的数据类型,比如
byte,char,int等占用1个slot,而double和Long这种64位的数据类型则需要分配2个slot,另外max_locals的值并不是所有局部变量所需要的内存数量之和,因为slot是可以重用的,当局部变量超过了它的作用域以后,局部变量所占用的slot就会被重用。方法参数、显示异常处理器的参数、方法体中定义的局部变量都要使用局部变量表来存放。

code_length代表了字节码指令的数量,而code表示的是字节码指令,从上图可以知道code的类型为u1,一个u1类型的取值为0x00-0xFF,对应的十进制为0-255,目前虚拟机规范已经定义了200多条指令。

exception_table_length以及exception_table分别代表方法对应的异常信息。

attributes_count和attribute_info分别表示了Code属性中的属性数量和属性表,从这里可以看出Class的文件结构中,属性表是很灵活的,它可以存在于Class文件,方法表,字段表以及Code属性中。

修改一下Sub中的InterB方法:

 @Override

    public int interB(int i){
        int x=0;
        try{
            x+=i;
            return x;
        }catch(Exception e){
            x=-1;
            return x;
        }finally{
            x=3;
        }
    }

大家不妨先猜一下这个函数的结果是什么?假如在try块中发生异常,结构又是什么?我相信对Java语言熟悉的朋友,肯定知道答案。

使用反编译工具查看:

public int interB(int);

    flags: ACC_PUBLIC

    Code:

      stack=2, locals=6, args_size=2

         0: iconst_0

         1: istore_2

         2: iload_2

         3: iload_1

         4: iadd

         5: istore_2

         6: iload_2

         7: istore        5

         9: iconst_3

        10: istore_2

        11: iload         5

        13: ireturn

        14: astore_3

        15: iconst_m1

        16: istore_2

        17: iload_2

        18: istore        5

        20: iconst_3

        21: istore_2

        22: iload         5

        24: ireturn

        25: astore        4

        27: iconst_3

        28: istore_2

        29: aload         4

        31: athrow

      Exception table:

         from    to   target    type

             2       9       14         Class java/lang/Exception

             2       9       25         any

            14      20    25         any

      LineNumberTable:

        line 35: 0

        line 37: 2

        line 38: 6

        line 43: 9

        line 38: 11

        line 39: 14

        line 40: 15

        line 41: 17

        line 43: 20

        line 41: 22

        line 42: 25

        line 43: 27

        line 44: 29

      LocalVariableTable:

        Start  Length  Slot  Name   Signature

               0      32        0      this         Lcom/gissky/clazz/Sub;

               0      32        1        i             I

               2      30        2        x            I

              15    10        3        e            Ljava/lang/Exception;

      StackMapTable: number_of_entries = 2

           frame_type = 255 /* full_frame */

          offset_delta = 14

          locals = [ class com/gissky/clazz/Sub, int, int ]

          stack = [ class java/lang/Exception ]

           frame_type = 74 /* same_locals_1_stack_item */

          stack = [ class java/lang/Throwable ]

}

从 args_size=2这条反编译代码,我们可以知道,在public int
interB(int
i)这个方法中有6个局部变量,2个参数,可是我们的函数中明明只有一个参数么……这是因为编译器会为每一个实例函数包括构造器添加一个参数this,在JVM调用该方法的时候会该形参传递一个实参—方法所在对象的自身。

Exception table:

from to target type

2 9 14 Class java/lang/Exception

2 9 25 any

14 20 25 any

上表表头表示,当字节码在form行到to行(不包括to行)出现类型为type的异常,则转到第target行继续处理。

从方法的异常表中,我们可以看到这个函数有3条执行路径:

这里我们插入阐述一下LineNumberTable表的含义:它表示Java源码行号与字节码行号之间的对应关系。

澳门新葡萄京官网首页 67

对照上图,我们能清晰的看出这3条路径。

知道了该方法执行的3条路径,我们也就知道刚才我们的那个问题有3个答案:没有异常是为x+i;try块中出现Exception类型的错误时,返回-1;出现Exception以外的任何异常方法非正常结束,没有返回值。

LocalVariableTable:

Start Length Slot Name Signature

0 32 0 this Lcom/gissky/clazz/Sub;

0 32 1 i I

2 30 2 x I

15 10 3 e Ljava/lang/Exception;

LocalVariableTable表示局部变量表,描述方法中局部变量。

如果你对返回的答案能理解的话,那么我相信你也肯定知道,我们函数中只有4个参数,但max_locals却等于6。不懂的话仔细看一下Code中字节码的执行过程变可以理解了。

一个方法在执行时需要多大的局部变量空间在编译时期就知道了,方法执行期间不会改变局部变量表的大小。

Signature 属性:

该属性是在JDK1.5新增的。该属性可用于类、属性表和方法表结构的属性表中。使用泛型签名如果包含了类型变量(Type
Variables)或参数化类型(Parameterized Types),则Signature
属性会为它记录泛型签名信息。当我们要泛型类中拿到泛型的实际类型的时候非常有用。

澳门新葡萄京官网首页 68

实例:

在使用Hibernate时,我习惯将为Dao层封装一个泛型基类,来放置一些通用的方法,而Hibernate有很多方法都要传递一个POJO的类型,然后进行查询,如load方法。我们构建这样的一个基类:

public abstract class BaseDaoImpl<T, PK extends Serializable>
extends HibernateDaoSupport implements BaseDao<T, PK>

那么load中要使用的POJO类型便是T的实际类型。怎么来那倒这个属性呢?这里边要使用到Signature属性了。

public abstract class BaseDaoImpl<T, PK extends Serializable> extends HibernateDaoSupport implements BaseDao<T, PK> {

    private Class<T> entityClass;

    @SuppressWarnings("unchecked")
    public BaseDaoImpl() {
        //class OrgDao extends BaseDaoImpl<Organization, String> implements OrgDao {}
        Class c = this.getClass(); //返回的是使用new创建的泛型类对应的对象的class对象。
        Type type = c.getGenericSuperclass(); //取得该对象的泛型类
        //取得泛型对应的真正的class,并放到数组中
        Type[] types = ((ParameterizedType)type).getActualTypeArguments();
        entityClass = (Class<T>) types[0];
    }

这时,getById中就可以直接使用了:

    public T getById(PK id) {
        return (T) getHibernateTemplate().load(entityClass, id);
    }

发表评论

电子邮件地址不会被公开。 必填项已用*标注