jvm之详解class类文件的结构

Class文件是一组以8位字节为基础单位的二进制流。各个数据项严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分割符。

Class文件结构采用类似C语言的结构来存储数据的,主要有两类数据项,无符号数和表。

 

Class文件中按照严格 的顺序排列的字节流包含的数据:

ClassFile{

u4 magic;

u2 minor_version;

u2 major_version;

u2 constant_pool_count;

cp_info constant_pool[count];

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];

}

 

  1. u4 magic表示魔数,表示这个文件的类型是一个CLass文件,Class文件对应的魔数是0xCAFEBABE.
  2. u2 minor_version 表示Class文件的次版本号
  3. u2 major_version 表示文件主版本号
  4. u2 constant_pool_count 表示常量池的数量
  5. cp_info 表示常量池 存储字面量和符号引用,其中字面量主要包括字符串,final常量的值或者某个属性的初始值。而符号引用主要存储类和接口的全限定名称,字段的名称描述符,方法的名称以及描述符

限定类名,就是类名全称,带包路径的用点隔开,例如: java.lang.String。

jvm之详解class类文件的结构

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,字段和方法的名称以及类型的符号引用

 

  1. u2 access_flags 表示类或者接口的访问信息

 

jvm之详解class类文件的结构

  1. u2 this_class 表示类的常量索引,指向常量池中CONSTRANT_Class_info的常量
  2. u2 super_class表示超类的索引,指向常量池中的CONSTRANT_Class_info的常量
  3. u2 interface_counts 表示接口的数量
  4. u2 interface[interface_counts]表示接口表,它里面每一项都指向CONSTRANT_Class_info常量
  5. u2 fields_count 表示类的实例变量和类变量的数量
  6. filed_info fields[fields_count]表示字段表的信息,其中字段表的结构如下

field_info{

u2 access_flags;

u2 name_index;

u2 descriptor_index;

u2 attributes_count;

attribute_info attributes[attributes_count];

}

access_flags表示字段的访问表示,比如字段是public ,private,protect

name_index表示字段名称,指向常量池中类型是CONSTRANT_UTF8_info的常量

descriptor_index表示字段的描述符,它也指向常量池中类型为 CONSTANT_UTF8_info的常量

attributes_count表示字段表中的属性表的数量,而属性表是则是一种用与描述字段,方法以及 类的属性的可扩展的结构,不同版本的Java虚拟机所支持的属性表的数量是不同的。

13 u2 methods_count表示方法的数量

14 method_info表示方法表

method_info{

u2 access_flags;

u2 name_index;

u2 descriptor_index;

u2 attribute_count;

attribute_info attributes[attribute_count];

}

access_flags表示方法的访问表示

name_index表示名称的索引

descriptor_index表示方法的描述符

attributes_count以及attribute_info类似字段表中的属性表

 

15 attribute_count表示属性表的数量

 

jvm之详解class类文件的结构

 

以实际实例来解释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{ }

 

编译后的TestClass.java对应的TestClass.class二进制结构图:

jvm之详解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

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的结够如下图所示:

CONSTRANT_Methodref_info{

u1 tag;

u2 class_index;

u2 name_and_type_index;

}

其中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 即可得到所有常量池中的常量,截图如下:

jvm之详解class类文件的结构

5.u2 access_flags表示类或者接口方面的访问信息

Class的访问标示是从0000010dh-0000010e,期值为0×0021,根据前面说的 各种访问标示的标志位,我们可以知道:0×0021=0×0001|0×0020 也即ACC_PUBLIC 和 ACC_SUPER为真

6.u2 this_class表示类的索引值,用来表示类的全限名称

0x00003对应常量池的第三个常量,第三个常量为 CONSTANT_Class_info类型的常量,通过它可以知道类的全限定名称为:com/ejushang/TestClass /TestClass

jvm之详解class类文件的结构

7.u2 super_class表示当前类的父类的索引值,,索引值所指向的常量

池中类型为CONSTANT_Class_info的常量,父类的索引值为0×0004, 查看常量池的第四个常量,可知TestClass的父类的全限定名称为:java/lang/Object

jvm之详解class类文件的结构

  1. interface_count 和interfaces[interfaces_count]表示接口数量以及具体的每一个接口

其中 0×0001表示接口数量为1,而0×0005表示接口在常量池的索引值,找到常量池的第五个常量,其类型为CONSTANT_Class_info,其 值为:com/ejushang/TestClass/Super

jvm之详解class类文件的结构

 

  1. fields_count 和field_info

field_count 表示类中field_info 表的数量,而field_info表示类的实例变量和类变量,不包含从父类继承过来的字段

field_info{

u2 access_flags;

u2 name_index;

u2 descriptor_index;

u2 attributes_count;

attribute_info attributes[attributes_count];

}

其中access_flags表示字段的访问标示,比如public,private,protected,static,final等

 

jvm之详解class类文件的结构

其中name_index 和 descriptor_index都是常量池的索引值,分别表示字段的名称和字段的描述符.

字段描述的规定:

jvm之详解class类文件的结构

其中最后一行,表示的是对一位数组的描述符,对于String[][]的描述符将是[[Ljava/lang/String,而对于int[][]的描述符为[[I.接下来的attributes_count以及attribute_info分别表示属性表的数量以及属性表

回归正题,查看字段数量

jvm之详解class类文件的结构

0x0002说明只有两个字段,

其中0x001A表示访问标识,0x0002 0x0008 0x0010可推断其为ACC_PRIVATE ,ACC_STATIC,ACC_FINAL,接下来0x0006和0x0007分别表示常量池中的第6个常量和第7个常量,分别为staticVar(字段名称) 和I(描述符int类型的变量),接下来0×0001表示staticVar这个字段表中的属性表的 数量,从上图可以staticVar字段对应的属性表有一个,0×0008表示常量池中的第8个常量,查看常量池可以得知此属性为

ConstantValue属性的格式为:

ConstantValue_attribute{

u2 attribute_name_index;

u4 attribute_length;

u2 constantvalue_index;

}

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

下面是private int instanceVar=0其中0×0002表示访问标示为ACC_PRIVATE,0x000A表示字段的名称,它指向常量池中的第10个常量,查看常量池可以知道字段名称为 instanceVar,而0×0007表示字段的描述符,它指向常量池中的第7个常量,查看常量池可以知道第7个常量为I,表示类型为 instanceVar的类型为I,最后0×0000表示属性表的数量为0.

jvm之详解class类文件的结构

 

jvm之详解class类文件的结构

  1. methods_count 和method_info

其中methods_count表示方法的数量,而method_info表示的方法表,其中方法表的结构如下

method_info{

u2 access_flags;

u2 name_index;

u2 descriptor_index;

u2 attributes_count;

attribute_info attributes[attributes_count];

}

方法表的access_flag所有标志位以及取值如下图所示:

jvm之详解class类文件的结构

 

0x0002表示有两个方法,分析第一个方法access_flag,name_index,descriptor_index

jvm之详解class类文件的结构

access_flag为0x0001,取值为ACC_PUBLIC,name_index为0x000B,查看常量池第11个常量,知道方法的名称为<init>,0x000C表示descriptor_index表示常量池的第12个常量,其值为()V,表示<init>方法没有参数和返回值,(编译器自动生成的实例构造器方法),接下来0x0001表示方法表有一个属性,

jvm之详解class类文件的结构

0x000D对应的常量池为Code,

code属性结构:

Code_attribute{

u2 attribute_name index;

u4 attribute_length;

u2 max_stack;

u2 max_locals;

u4 code_length;

u1 code[code_length];

u2 exception_table_length;

{

u2 start_pc;

u2 end_pc;

u2 handler_pc;

u2 catch_type;

}exception_table[exception_table_length];

u2 attributes_count;

attribute_info_attributes[attributes_count];

}

其中0表示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的结构如下图所示

jvm之详解class类文件的结构

 

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

jvm之详解class类文件的结构......省略第二个方法分析步骤

看看最后Class文件属性的分析:

0x0011表示属性的名称

jvm之详解class类文件的结构

查看常量池可知属性名称为SourceFile

SourceFile_attribute{

u2 attribute_name_index;

u4 attribute_length;

u2 sourcefile_index;

}

其中attribute_length为属性的长度,sourcefile_index指向常量池中值为源代码文件名称的常量

其中attribute_length为0×00000002表示长度为2个字节,而soucefile_index的值为0×0012,查看常量池的第18个常量可以知道源代码文件的名称为TestClass.java

其实大多的篇幅来源于《jvm高级特性与最佳实战》,但是更多详尽的解析来源于下面这篇文章,因为在阅读jvm实战讲解的篇幅中有些地方讲解不够详尽,并不太明白,这篇文章的作者对class结构的解释更加清晰一些。

https://www.jb51.net/article/35187.htm