一、魔数、版本号、常量池。
1、使用javap -verbose
命令分析一个字解码文件时,将会分析字节码文件的魔数
、版本号
、常量池
、类信息
、类的方法
、类中的方法信息
、类变量与成员变量
等信息。
2、魔数:所有的.class字节码文件的前4个字节都是魔数,魔数为固定值:0xCAFEBABE
。
3、魔数之后的4个字节为版本号信息,前两个字节表示minor version(次版本号) 。
4、常量池(constant pool):紧接着主版本号之后就是常量池入口,一个java类中的定义的很多信息都是由常量池来维护和描述的,可以将常量池看作是class文件的资源仓库,比如说java类中的定义的方法与变量信息,都是存储在常量池中。常量池中主要存储两类常量:字面量和符号引用,字面量如文本字符串、java中声明为final的常量值等,而符号引用如类的接口的全局限定名,字段的名称和描述符、方法的名称和描述符等。
5、常量池的总体结构:java类的所对应的常量池主要由常量池与常量数组这两部分共同构成。常量池数量紧跟在主版本后面,占据两个字节。常量池数组则紧跟着常量池数量之后。常量池数组与一般数组不同的是,常量池数组中不同元素的类型、结构都是不同的,长度当然也就不同;但是,每一种元素第一个数据都是一个u1类型,该字节是一个标志位,占据1个字节。JVM在解析常量池时,会根据这个u1类型来获取元素的具体类型。值得注意的是常量池数组中元素的个数 = 常量池数 - 1 (其中0不使用)。目的是满足某些常量池索引值的数据在特定情况下需要表达“不引用任何一个常量池”的含义;根本原因在于,索引为0也是一个常量(保留常量),只不过它不位于常量表中,这个常量就对应null值;所以,常量池从1而非0开始。
6、在JVM中每个字段都有对应的描述信息,描述信息主要的作用是描述字段的数据类型、方法参数列表(包括数量、类型与顺序)与返回值。根据描述规则,基本数据类型和代表无返回值的void类型都用一个大写字符表示,对象类型则使用字符L加对象的全限定名称来表示。为了压缩字节码文件的体积,对于基本数据类型,JVM都只是使用一个大写字母来表示,如下所示:B-byte、C-char、D-double、F-float、I-int、J-long、S-short、Z-boolean、V-void、L-对象类型、如Ljava/lang/String; 对于数组类型来说,每一个维度使用一个前置的[来表示,如int[]被记录为[I,二维数组的String[]()被记录为[[Ljava/lang/String。
7、用描述符来描述方法时,按照先参数列表后返回值的顺序来描述。参数列表按照参数的严格顺序放在一组()之内。如方法:String getRealnamebyIdAndNickname(int id,String name)的描述符为:(I,Ljava/lang/String;)Ljava/lang/String
上面的表中描述了11种数据类型的结构,其实在jdk1.7之后又增加了3种
CONSTANT_MethodHandle_info
,CONSTANT_MethodType_info
,以及CONSTANT_invokeDynamic_info
这样一共14种。class字节码中有两种数据类型:1、字节数据直接量,这是基本的数据类型。共细分为u1、u2、u4、u8四种,分别代表连续的1个字节,2个字节,4 个字节,8个字节组成的整体数据。
表(数组):表是由多个基本数据或其他表按照既定顺序组成的大的数据集合。表是有结构的,它的结构体现在:组成表的成分所在位置和顺序都是已经严格定义好的。
二、访问标志符(access_flags)、当前类名(this.class)、父类(super.class)、接口数量(Interfaces_count)
访问标志符信息包括该class文件是类还是接口,是否被定义成public,是否是abstract如果是类,是否被声明成final.通过上面的源代码,我们知道该文件是类并且是public。0x00 02为ACC_PRIVATE。
0x0021:是0x 0021和0x 0001的并集,表示ACC_PUBLIC与ACC_SUPER。
三、字段表集合(files)
字段表用于描述类和接口中声明的变量。这里的字段包含了类级别变量以及实例变量,但是不包括方法内部声明的局部变量。
字段表结构:
filed_info{
u2 access_flags;0002
u2 name_index;0005
u2 descriptor_index;0006
u2 attribute_count;0000
u2 attribute_info attributes[attribute_count]
}
六、关于方法的结构与解析(Methodsd)
方法中的每个属性都是一个attribut_info结构。JVM预定义了部分attribute,但是编译器自己也可以实现自己的attribute写入class文件里,供运行使用。不同的attribute通过attribute_name_index来区分。
attribute_info{
u2 attribute_name_index;
u4 attribute_length;
u1 info [attribute_length]
}
Code结构:code attribute的作用是保存该方法的结构,如所对应的字节码:
attribute_length
:表示attribute所包含的字节数,不包含attribute_name_index
和attribute_length
字段。max_stack
:表示这个方法运行的任何时刻所能达到的操作数栈最大的深度。max_locals
:表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量。ode_length
:表示该方法所包含的字节码的字节数以及具体的指令码。具体字节码即是该方法被调用时,虚拟机所执行的字节码。exception_table
:这里存放的是异常处理的信息。。每个
exception_table
表示由start_pc
,end_pc
,handle_pc
,catch_type
组成。start_pc
的end_pc
表示在code数组中的从start_pc
到end_p
c处(包含start_pc
,不包含end_pc
)的指令抛出的异常会由这个异常表示来处理。handle_pc
表示处理异常的代码的开始处。catch_type
表示会被处理异常类型,它指向常量池里的一个异常类。当catch_type
为0
时,表示处理所有异常。附加属性:
LineNumberTable
:这个属性用来表示code数组中的字节码和java代码行数之间的关系。这个属性可以用来在调试的时候定位代码执行的行数。
LineNumberTable_attribute{
u2 attribute_name_index;
u2 attribute_length;
u2 line_number_table_length;
{
u2 start_pc;
u2 line_number;
}line_number_table[line_number_table_length]
}
方法属性表:
method_info{
u2 access_flags;
u2 name_index;
u2 descriptar_index;
u2 affributes_count;
attribute_info attributes[attributes_count]
}