首先看Class中包含哪些信息简单的说所有java文件中有的信息class文件都有,编译器帮我们将java文件转化成了JVM能看懂的class格式而已
Class 概述
Class文件是一组以8位字节为基础的二进制流,各个数据项目按照严格顺序紧凑排列在Class文件中。
所有的16位,32位,64位长度的数据将被构造成2个,4个,8个字节单位来标示。
ClassFile结构
类型 | 名称 | 数量 |
---|---|---|
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 |
class格式说明
- magic:魔数,魔数的唯一作用是确定这个文件是否为一个能被虚拟机所接受的Class文件。魔数值固定为0xCAFEBABE
- minor_version、major_version: 分别为Class文件的副版本和主版本
- constant_pool_count: 常量池计数器,constant_pool_count的值等于constant_pool表中的成员数加1
- constant_pool[]: 常量池,constant_pool是一种表结构,它包含Class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其它常量。常量池不同于其他,索引从1开始到constant_pool_count -1
- access_flags: 访问标志,access_flags是一种掩码标志,用于表示某个类或者接口的访问权限及基础属性
- this_class: 类索引,this_class的值必须是对constant_pool表中项目的一个有效索引值
- super_class: 父类索引
- interfaces_count: 接口计数器,interfaces_count的值表示当前类或接口的直接父接口数量
- interfaces[]: 接口表,interfaces[]数组中的每个成员的值必须是一个对constant_pool表中项目的一个有效索引值,它的长度为interfaces_count
- fields_count: 字段计数器
- fields[]: 字段表,fields[]数组中的每个成员都必须是一个fields_info结构的数据项
- methods_count: 方法计数器
- methods[]: 方法表,methods[]数组中的每个成员都必须是一个method_info结构的数据项
- attributes_count: 属性计数器
- attributes[]: 属性表,attributes表的每个项的值必须是attribute_info结构
废话不多说HelloWorld搞起
public class HelloWorld
{
String str = "";
public String getStr()
{
return str;
}
public void setStr(String str)
{
this.str = str;
}
}
编译成class文件以后,只用javap -verbose HelloWorld.class 指令可以查看当前class的内容
同时使用UE打开class文件
我们来一起看下ClassFile结构
前4个字节为魔数,也就是0xCAFEBABE,这里都是十六进制
魔数后2个字节为副版本号,这里副版本号是0
主版本号0x0033,转为十进制,主版本号是51 标示当前class是通过jdk 1.7编译的,0x32是jdk1.6 0x31是jdk1.5
这两个字节是常量池计数器,常量池的数量为0x001A,转为十进制是26,也就是说常量池索引为1~26
从常量池开始
常量池计数器后面紧跟着就是常量池的内容
所有的常量池项都具有如下通用格式:
cp_info
{
u1 tag;
u1 info[];
}
CONSTANT_Class_info
{
u1 tag;
u2 name_index;
}
CONSTANT_Fieldref_info
{
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_Methodref_info
{
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_InterfaceMethodref_info
{
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_NameAndType_info
{
u1 tag;
u2 name_index;
u2 descriptor_index;
}
CONSTANT_Utf8_info
{
u1 tag;
u2 length;
u1 bytes[length];
}
CONSTANT_MethodHandle_info
{
u1 tag;
u1 reference_kind;
u2 reference_index;
}
后面的0x07对应tag找到是CONSTANT_Class,标示接下来的是一个class的信息。后面的 00 02 是class的name_index 标示指向常量池的第二个常量。我们再看第二个常量
第二个常量是01开头,我们查看常量类型表中对应是Utf-8,再按照utf-8的结构,后面的00 0A代表了这个utf-8的长度这里长度转换为10进制是11,后面紧跟着utf-8的实际内容
再后面0x 07,是常量池的下一个产量,也是一个class信息,后面跟00 04,name_index执行常量池的
第4个常量。
第4个常量又是utf-8,后面长度为 0x10 十进制为16,接下来的为实际内容
接下来都可以按照此方法分析。
直观结果可以通过javap指令查看
常量池后面紧跟的2个字节是Access Flag,这个表示用于标示类或接口层次的访问信息,如这个Class是类还是接口,是否为public类型,是否定义为abstrace类型。
标志名称 标志值 含义
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 标识这是一个枚举
我们这里0021标示为public Class
接下来的是类索引,父类索引与接口索引集合
this_class,super_class,interfaces_count,interfaces
类索引
为2个字节
这里为00 01,指向常量池中第一个常量,之前我们分析过常量池中第一个常量为Class类型,内容指向第二个常量UTF-8的HelloWorld。
标示当前名为HelloWorld
父类索引
也是2个字节
指向常量中第三个常量,对应内容为java/lang/Object
接口数量
字段表集合
00 01 标示字段数量为1
字段表的格式如下
类型 | 名称 | 数量 |
---|---|---|
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_coun |
accessFlags为 00 00 当时当前字段无修饰符
字段修饰符格式如下
标志名称 | 标志值 | 含义 |
---|---|---|
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 |
name_index为 00 05指向常量池中的第五个常量
第5个常量为str,变量名为str
descriptor_index指向常量池第6个变量,为Ljava/lang/String类型
attributes_count(属性计数器,占2字节,0x0000,所以该字段没有额外需要描述的信息)
方法集合
method_count: 00 03 有3个方法
methods:方法表集合
类型 | 名称 | 数量 |
---|---|---|
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_coun |
access flags的定义见下表
标志名称 | 标志值 | 含义 |
---|---|---|
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 | 字段是否为编译器自动产生 |
这里方法access flags 为 00 01 说明方法为public的
name_index为00 07,方法名指向常量中第7个常量方法名为, descriptor_index为常量池第8个常量()V
attributes_count 为 00 01标示这个方法的属性表集合中有一个属性。属性名称为接下来2位0x0009,指向常量池中第9个常量:Code
接下来4位为 00 00 00 3D标示Code属性值的字节长度为3D,接下来为00 02标示该方法的操作数栈的深度最大值为2.
00 01标示该方法的局部变量占用空间为1.
接下来4位00 00 00 0B 为机器编译生成字节码指令的长度为11,后面11个字节就是字节码指令(字节码指令可查询虚拟机字节码指令表),这里字节码指令长度用4个字节标示,所有字节码指令超长Class编译会失败的。
再接下来为 00 00标示Code属性异常表结合为空。
再后面为 00 02,,说明Code带有2个属性, 00 10即为Code属性第一个属性的属性名成指向常量池中第16个常量
接下来的00 00 00 0E 标示LinueNumberTable属性值所占字节长度为15.接下来2位 00 03标示该line number table中有3个line number table表,start pc为 00 00 line number第 00 01个为00 04 第 00 02个为 00 0A
再后面的 00 01又是第二个方法的access flags,接着开始第二个方法。