JVM学习笔记 -- 类文件结构

  Java提出了"一次编写,到处运行"的口号,同一份程序可以在不同的平台上运行,实现语言无关性的基础是虚拟机和字节码存储格式,Java虚拟机不和包括Java在内的任何语言绑定。根据《深入理解Java虚拟机》中的内容,下面以《Java虚拟机规范(第2版)》,对应JDK1.4的Java虚拟机说明类文件的结构。

1、Class类文件结构

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

1.1 数据结构和文件格式

  Class文件中只有2种数据结构:无符号数和表,无符号数是基本的数据类型,以u1、u2、u4、u8代表1个字节、2个字节、4个字节、8个字节的无符号数,而表是由无符号数和其他表组成的结构,整个Class文件本质上就是一张表。下面是Class文件中的数据项:

类型名称数量
u4magic1
u2minor_version1
u2major_version1
u2constant_pool_count1
cp_infoconstant_poolconstant_pool_count
u2access_flags1
u2this_class1
u2super_class1
u2interfaces_count1
u2interfacesinterfaces_count
u2fields_count1
field_infofieldsfields_count
u2methods_count1
method_infomethodsmethods_count
u2attributes_count1
attributes_infoattributesattributes_count

  Class文件中字节长度、代表意义、先后顺序等细节都是严格规定不可改变的。下面具体介绍这些数据项。

1.2 魔数和Class文件版本

  Class文件前4个字节的魔数是用于身份识别,确定文件能否被虚拟机接受。Java Class文件的魔数是0xCAFEBABE。

  接下来的是Class文件的次版本号和主版本号。次版本号是小数点后面的部分,主版本号是小数点前面的部分。比如JDK1.2支持45.0~46.65535,如果有个Class文件的版本号是45.00,那么次版本号就是0,主版本号是45。

1.3 常量池

   constant_pool_count是Class文件常量池中常量的数量,如果这个数值是6,说明常量池中有5个常量,因为第0个是空出来用于表达"不引用任何一个常量池项目"的含义。常量池中主要放2类常量:字面量和符号引用。其中符号引用有下面3类:

1、类和接口的全限定名:把类的全名由"."改为"/"

2、字段的名称和描述符:

3、方法的名称和描述符:

  Class文件中字段和方法的符号引用需要经过运行期转换后才能得到真正的内存入口地址,当虚拟机运行时,从常量池获取对应的符号引用,然后在类创建或运行时解析、翻译到具体的内存地址。JDK1.7中一共有下面14种常量:

类型标志描述
CONSTANT_Utf8_info1UTF-8编码的字符串
CONSTANT_Integer_info3整型字面量
CONSTANT_Float_info4浮点型字面量
CONSTANT_Long_info5长整型字面量
CONSTANT_Double_info6双精度浮点型字面量
CONSTANT_Class_info7类或接口的符号引用
CONSTANT_String_info8字符串类型字面量
CONSTANT_Fieldref_info9字段的符号引用
CONSTANT_Methodref_info10类中方法的符号引用
CONSTANT_InterfaceMethodref_info11接口中方法的符号引用
CONSTANT_NameAndType_info12字段或方法的部分符号引用
CONSTANT_MethodHandler_info15表示方法句柄
CONSTANT_MethodType_info16表示方法类型
CONSTANT_InvokeDynamic_info18表示一个动态方法调用点

  这14种常量的表的第一位都是一个u1类型的标志位,用于表示常量类型。引用书中的Java代码进行分析:

package or.fenixsoft.clazz;

public class TestClass {
    private int m;
    public int inc(){
        return m + 1;
    }
}

  对应解析出来的Class文件字节码:

......
Constant pool:
const #1 = class                #2;  //org/fenixsoft/clazz/TestClass
const #2 = Asciz                org/fenixsoft/clazz/TestClass;
const #3 = class                #4;  //java/lang/Object
const #4 = Asciz                java/lang/Object
const #5 = Asciz                m;
const #6 = Asciz                I;
const #7 = Asciz                <init>;
const #8 = Asciz                ()V;
const #9 = Asciz                Code;
const #10 = Method              #3.#11;  //java/lang/Object."<init>":()V
const #11 = NameAndType         #7:#8;  //"<init>":()V
const #12 = Asciz               LineNumberTable;
const #13 = Asciz               LocalVariableTable
const #14 = Asciz               this;
const #15 = Asciz               Lorg/fenixsoft/clazz/TestClass;;
const #16 = Asciz               inc;
const #17 = Asciz               ()I;
const #18 = Field               #1.#19;  //org/fenixsoft/clazz/Testclass.m:I
const #19 = NameAndType         #5:#6;  //m:I
const #20 = Asciz               SourceFile;
const #21 = Asciz               TestClass.java;

   下面是对应的常量池结构:

1 CA FE BA BE 00 00 00 32
2 00 16 07 00 02 01 00 1D
3 6F 72 67 2F 66 65 6E 69
4 78 73 6F 66 74 2F 63 6C
5 ......

  第二行的07开始就是常量池项目,它的常量结构如下表:

类型名称数量
u1tag1
u2name_index1

  7对应的是CONSTANT_Class_info,接下来的0002表示值为第二个常量。第二个常量是01,即UTF-8编码的字符串,它的结构为:

类型名称数量
u1tag1
u2length1
u1byteslength

  001D即是29,接下来29个字节组成的字符串即是第二个变量代表的字符串。CONSTANT_Utf8_info的长度类型是u2,代表字段名、方法名不能超过65535,正常也不会取这么长的名字吧。其他常量格式不给出,都是按照这样的方式存储的。

1.4 访问标志

  访问标志用于标识类或接口层次的访问信息,16个标志位定义了8个,没有使用到的一致为0:

标志名称标志值含义
ACC_PUBLIC0x0001是否为public类型
ACC_FINAL0x0010是否声明为final
ACC_SUPER0x0020是否允许使用invokespecial指令新语义
ACC_INTERFACE0x0200是否是接口
ACC_ABSTRACT0x0400是否是abstract类型
ACC_SYNTHETIC0x1000标识类非由用户代码产生
ACC_ANNOTATION0x2000是否是注解
ACC_ENUM0x4000是否是枚举

1.5 类索引、父类索引、接口索引集合

  this_class是类的全限定名,super_class是父类的全限定名,接口集合则是根据implements后面的接口顺序从左到右排序。这几项都指向CONSTANT_Class_info常量。

1.6 字段表集合

  字段表用于描述类或接口的变量,方法内部定义的局部变量不包含在内。下面是fields字段表的结构:

类型名称数量
u2access_flags1
u2name_index1
u2descriptor_index1
u2attributes_count1
attribute_infoattributesattributes_count
1.6.1 access_flags

  字段修饰符放在access_flags中,具体如下表:

标志名称标志值含义
ACC_PUBLIC0x0001字段是否public
ACC_PRIVATE0x0002字段是否private
ACC_PROTECTED0x0004字段是否protected
ACC_STATIC0x0008字段是否static
ACC_FINAL0x0010字段是否final
ACC_VOLATILE0x0040字段是否volatile
ACC_TRANSIENT0x0080字段是否transient
ACC_SYNTHETIC0x1000字段是否由编译器自动产生
ACC_ENUM0x4000字段是否enum

1.6.2 name_index

  这个表示字段的简单名称,简单名称是指没有类型和参数修饰的方法或字段名,比如常用的有个toString的简单名称。

1.6.3 descriptor_index

  描述符,用于描述字段的数据类型、方法的参数列表和返回值。基本数据类型和代表无返回值的void类型用一个大写字符表示,而对象类型则用字符L加对象的全限定名来表示,如下表:

标识字符含义
B基本类型byte
C基本类型char
D基本类型double
F基本类型float
I基本类型int
J基本类型long
S基本类型short
Z基本类型boolean
V特殊类型void
L对象类型

  对于数组类型,每一个维度用"["表示,如定义一个"java.lang/String[][]"类型,则是"[[Ljava/lang/String",一个整型数组"int[]",则是"[I"。

1.6.4 attributes

  属性表,用来存储一些额外的信息,如果有字段"final static int m = 123",则会存在一个名称为ConstantValue的属性,指向常量123。

1.7 方法表集合

  方法表的结构和字段表一致,依次包括access_flags、name_index、descriptor_index、attributes_count、attributes。方法表集合可能会有编译器自动添加的方法,比如类构造器"<clinit>"方法和实例构造器"<init>"方法。

1、类构造器:编译器自动收集类中所有类变量和静态语句块中的语句合并而成,顺序为源代码顺序。

2、实例构造器:编译器自动收集类中所有实例变量和非静态语句块中的语句合并而成。

1.7.1 access_flags

  方法表的访问标志与字段有所区别:

标志名称标志值含义
ACC_PUBLIC0x0001方法是否为public
ACC_PRIVATE0x0002方法是否为private
ACC_PROTECTED0x0004方法是否为protected
ACC_STATIC0x0008方法是否为static
ACC_FINAL0x0010方法是否为final
ACC_SYNCHRONIZED0x0020方法是否为synchronized
ACC_BRODGE0x0040方法是否为由编译器产生的桥接方法
ACC_VARARGS0x0080方法是否接受不定参数
ACC_NATIVE0x0100方法是否为native
ACC_ABSTRACT0x0400方法是否为sbstract
ACC_STRICTFP0x0800方法是否为strictfp
ACC_SYNTHETIC0x1000方法是否为编译器自动产生的

1.7.2 descriptor_index

  描述符先按照参数列表,后返回值的顺序描述,参数列表按照参数顺序方法小括号"()"中,方法void inc(int a,char b)的描述符为"(IC)V",java.lang.String.toString()的,描述符为"()Ljava/lang/String"

1.7.3 attributes

  方法里的Java代码会存放在属性表中,在一个"Code"的属性中。

1.8 属性表集合

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

1.8.1 Code属性

  Code属性就是方法表中存放方法代码的地方。Code属性结构为:

类型名称数量
u2attribute_name_index1
u4attribute_length1
u2max_stack1
u2max_locals1
u4code_length1
u1code_lengthcode_length
u2exception_table_length1
exception_infoexception_tableexception_table_length
u2attributes_count1
attribute_infoattributesattributes_count

1、attribute_name_index

  这个属性项是一个指向CONSTANT_Utf8_info常量的索引,值为"Code",代表属性名称。

2、attribute_length

  属性值的长度,等于整个属性表的长度减去6个字节(属性名和属性长度占据的6个字节)。

3、max_stack

  代表操作数栈深度的最大值,虚拟机运行时根据这个值分配栈帧中的操作栈深度。

4、max_locals

  局部变量表所需的存储空间。单位是Slot。长度不超过32位的数据类型,每个局部变量占用一个Slot,double和long占用2个Slot。局部变量占据Slot的和不等于max_locals,代码执行超出一个局部变量的作用域时,其占据的Slot可以复用。

5、code

  用于存储源代码编译生成的字节码指令,指令是u1类型的单字节。有些指令是带有参数的,虚拟机知道如何理解。另外code_length虽然定义 了u4类型的长度,实际虚拟机规范限制了一个方法的字节码不能超过65535条。

6、exception_table

  方法的异常处理表,它的结构为:

类型名称数量
u2start_pc1
u2end_pc1
u2handler_pc1
u2catch_type1

  含义为:如果字节码在第start_pc行到end_pc(不包括end_pc)出现了类型为catch_type或其子类的异常,则转到第handler_pc行继续处理。当catch_type的值为0是,任何异常都要转到handler_pc处理。

1.8.2 Exception属性

  这个属性跟Code属性平级,与上面的异常表不同,是方法描述时在throws关键字后面列举的异常。结构为:

类型名称数量
u2attribute_name_index1
u4attribute_length1
u2number_of_exception1
u2exception_index_tablenumber_of_exception

  exception_index_table表示抛出的受检异常,是一个指向常量池CONSTANT_Class_info型常量的索引。

转载于:https://www.cnblogs.com/liuwy/p/11069594.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值