深入理解java虚拟机——类文件结构

来源:互联网 发布:淘宝二级域名的概念 编辑:程序博客网 时间:2024/05/22 13:37

深入理解java虚拟机——类文件结构

  • 深入理解java虚拟机类文件结构
    • 魔数
    • 版本号
    • 常量池
    • 访问标志
    • 类索引父类索引与接口索引集合
    • 字段表集合
    • 方法表集合
    • 属性表集合

Class 文件是一组以 8 位字节为基础单位的二进制流,其文件格式采用一种类似C语言「 结构体 」的结构存储数据,这种伪结构体只有两种数据类型:无符号数
无符号数以 u1,u2,u4,u8 分别代表 1 个字节、2 个字节、4 个字节和 8 个字节的无符号数。
表是由多个无符号数或其他表构成的复合数据结构,习惯以 _info 结尾。整个 Class 文件本质上就是一张表。
@Class文件格式|center
无论无符号数还是表,当需要表示同一类型但数量不定的多数据时,常会采用一个前置容量计数器加若干连续数据项的形式。这一系列连续的某一类型数据称为某一类型的集合。

本文将以此 java 代码生成的 Class 文件为例。

public class TestClass {    private int m ;    public int inc(){        return m+1;    }}
cafe babe 0000 0034 0016 0a00 0400 12090003 0013 0700 1407 0015 0100 016d 01000149 0100 063c 696e 6974 3e01 0003 28295601 0004 436f 6465 0100 0f4c 696e 654e756d 6265 7254 6162 6c65 0100 124c 6f63616c 5661 7269 6162 6c65 5461 626c 65010004 7468 6973 0100 154c 436c 6173 7346696c 652f 5465 7374 436c 6173 733b 01000369 6e63 0100 0328 2949 0100 0a53 6f757263 6546 696c 6501 000e 5465 7374 436c6173 732e 6a61 7661 0c00 0700 080c 00050006 0100 1343 6c61 7373 4669 6c65 2f546573 7443 6c61 7373 0100 106a 6176 612f6c61 6e67 2f4f 626a 6563 7400 2100 03000400 0000 0100 0200 0500 0600 0000 02000100 0700 0800 0100 0900 0000 2f00 01000100 0000 052a b700 01b1 0000 0002 000a0000 0006 0001 0000 0006 000b 0000 000c0001 0000 0005 000c 000d 0000 0001 000e000f 0001 0009 0000 0031 0002 0001 00000007 2ab4 0002 0460 ac00 0000 0200 0a000000 0600 0100 0000 0900 0b00 0000 0c000100 0000 0700 0c00 0d00 0000 0100 10000000 0200 11

魔数:


cafe babe

每个 Class 文件头 4 字节称为魔数(magic_number),用于确定这个文件是否为一个 Class 文件,具有固定的值:0xCAFEBABE(caffe baby~)

版本号:


cafe babe 0000 0034

接下来的 4 字节存储 Class 的版本号。
前 2 字节 0x0000 代表次版本号(minor_version),后 2 字节代表主版本号(major_version),0x0034 即十进制 52,代表 JDK 版本为 1.8。

常量池:


0016
常量池可以理解为 Class 文件的「 资源仓库 」。
由于常量池的常量数量不是固定的,因此在常量池的入口处,即主版本号后两个字符代表常量池容量计数值(constant_pool_count)。
常量池容量计数从 1 开始,第 0 号常量用于在特定情况下需要表达 “ 不引用任何一个常量池项目 ”。这里常量池容量为 0x0016,代表共有 21 个常量。常量池主要存放两种常量:字面量符号引用
字面量即字符串、声明为 final 的常量值等。
符号引用包括三类常量:

类和接口的全限定名

字段名称和描述符

方法名称和描述符

常量池中的每一个常量都是一个表,共有 14 种各不相同的表结构。每个表第一位均为 u1 类型的标志位,代表当前常量的类型:

类型 标志 描述 CONSTANT_Utf8_info 1 UTF-8编码字符串 CONSTANT_Integer_info 3 int类型字面值 CONSTANT_Float_info 4 float类型字面值 CONSTANT_Long_info 5 long类型字面值 CONSTANT_Double_info 6 double类型字面值 CONSTANT_Class_info 7 类或接口符号引用 CONSTANT_String_info 8 String类型字面值 CONSTANT_Fieldref_info 9 对一个字段的符号引用 CONSTANT_Methodref_info 10 类中声明方法的符号引用 CONSTANT_InterfaceMethodref_info 11 接口中声明方法的符号引用 CONSTANT_NameAndType_info 12 字段或方法的部分符号引用

常量项结构表

常量 项目 类型 描述 CONSTANT_Utf8_info 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 指向该字段或方法描述符常量项的索引
0a00 0400 12

第 1 项常量标志位 0x0a ,即十进制 10,对应 CONSTANT_Methodref_info,代表方法的符号引用。
第 1 个 index —— 0x0004 为 CONSTANT_Utf8_info 类型常量,代表此方法所在类的全限定名,指向常量池的第 4 个常量。第 2 个 index —— 0x0012 为 CONSTANT_NameAndType_info 类型,指向第 18 个常量。

09 0003 0013

第 2 项常数标志位 0x09,代表 CONSTANT_Fieldref_info,即字段的符号引用。

0700 14

第 3 项常数标志位 0x07,代表 CONSTANT_Class_info 。 index 为类的全限定名,指向第 20 个常量。

07 0015

第 4 项常数标志位 0x07,index 指向第 21 个常量。

0100 016d

第 5 项常数标志位 0x01,代表 CONSTANT_Utf8_info 。length 为 0x0001,即 1 字节。 bytes 为长度 1 字节的字符串:0x6d,对应ASCII码的 ‘ m ’ 。

0100 0149

同理,第 6 项常数为 ‘ I ’。

0100 063c 696e 6974 3e

第 7 项——“ < init > ”

01 0003 2829 56

第 8 项——“ ()V ”

01 0004 436f 6465

第 9 项——“ Code ”

0100 0f4c 696e 654e 756d 6265 7254 6162 6c65

第 10 项——“ LineNumberTable ”

0100 124c 6f63 616c 5661 7269 6162 6c65 5461 626c 65

第 11 项——“ LocalVariableTable ”

01 0004 7468 6973

第 12 项——“ this ”

0100 154c 436c 6173 7346 696c 652f 5465 7374 436c 6173 733b

第 13 项——“ LClassFile/TestClass; ”

0100 0369 6e63

第 14 项——“ inc ”

0100 0328 2949

第 15 项——“ ()I ”

0100 0a53 6f75 7263 6546 696c 65

第 16 项——“ SourceFile ”

01 000e 5465 7374 436c 6173 732e 6a61 7661

第 17 项——” TestClass.java “

0c00 0700 08

第 18 项标志位对应 CONSTANT_NameAndType_info。
第 1 个 index 指向第 7 个常数,第 2 个 index 指向第 8 个常数。即 “ < init >:()V ”

0c 0005 0006

第 19 项——“ m:I ”

0100 1343 6c61 7373 4669 6c65 2f54 6573 7443 6c61 7373

第 20 项——“ ClassFile/TestClass ”

0100 106a 6176 612f 6c61 6e67 2f4f 626a 6563 74

第 21 项——“ java/lang/Object ”

当然,每次直接分析 Class 文件效率太低了~,java 提供了专门用于分析 Class 文件的工具 javap
@javap|center

访问标志:


常量池结束后,接下来的 2 个字节代表访问标志( access_flags )。用于识别一些类或接口的「访问信息 」。
包括 Class 是类还是接口,是否为 public,是否定义为 abstract 类型,是否声明为 final 等。

标志名 标志值 标志含义 针对的对象 ACC_PUBLIC 0x0001 public类型 所有类型 ACC_FINAL 0x0010 final类型 类 ACC_SUPER 0x0020 使用新的invokespecial语义 类和接口 ACC_INTERFACE 0x0200 接口类型 接口 ACC_ABSTRACT 0x0400 抽象类型 类和接口 ACC_SYNTHETIC 0x1000 该类不由用户代码生成 所有类型 ACC_ANNOTATION 0x2000 注解类型 注解 ACC_ENUM 0x4000 枚举类型 枚举
00 21

在本例中 ACC_PUBLIC,ACC_SUPER 为真。
将标志为真的 flag 进行掩码实现:

0x0020|0x0001 = 0x0021

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


Class 文件中由这三项数据确定这个类的「 继承关系 」。类索引为这个类的全限定名,父类索引为这个类的父类,除 Object 外,所有类的父类索引不能为0。
类索引、父类索引均是 u2 类型的数据,接口索引集合为一组 u2 类型的集合。入口第一项 u2 类型数据为接口计数器。

00 0300 0400 00

0x00030x00040x0000 对应常量池的第 3 项与第 4 项,接口为空。

字段表集合


字段表(field_info)集合用于描述接口或者类中声明的「 变量 」,包括类级变量以及实例级变量,但不包括方法内部声明的局部变量。
一个字段使用一个字段表描述。字段表集合的入口 2 个字符同样代表容量计数值。

字段表格式:

类型 名称 数量 u2 access_flags 1 u2 name_index 1 u2 descriptor_index 1 u2 attributes_count 1 attribute_info attributes attributes_count

字段修饰符放在 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_VOLATILE 0x0040 字段是否为volatile ACC_TRANSIENT 0x0080 字段是否为transient ACC_SYNTHETIC 0x1000 字段是否为编译器自动产生 ACC_ENUM 0x4000 字段是否为enum

接下来是两项索引值:name_index、descriptor_index,代表字段的简单名称和方法的描述符。

描述符 含义 B 基本数据类型byte C 基本数据类型char D 基本数据类型double F 基本数据类型float I 基本数据类型int J 基本数据类型long S 基本数据类型short Z 基本数据类型boolean V 基本数据类型void L 对象类型
00 0100 0200 0500 0600 00

示例类中字段共 0x0001 个。access_flags 值为 0x0002,代表为 private。name_index为‘ m ’。descriptor_index 为‘ I ’。attributes_count 为 0 。即“privat int m;”

方法表集合


Class 文件对方法的描述与对字段的描述十分相似。方法表的结构与字段表结构完全相同~

类型 名称 数量 u2 access_flags 1 u2 name_index 1 u2 descriptor_index 1 u2 attributes_count 1 attribute_info attributes attributes_count 标志名 标志值 标志含义 ACC_PUBLIC 0x0001 方法是否为public ACC_PRIVATE 0x0002 方法是否为private ACC_PROTECTED 0x0004 方法是否为protected ACC_STATIC 0x0008 方法是否为static ACC_FINAL 0x0010 方法是否为final ACC_SYHCHRONRIZED 0x0020 方法是否为synchronized ACC_BRIDGE 0x0040 方法是否为编译器产生的桥接方法 ACC_VARARGS 0x0080 方法是否接受不定参数 ACC_NATIVE 0x0100 方法是否为native ACC_ABSTRACT 0x0400 方法是否为abstract ACC_STRICTFP 0x0800 方法是否为strictfp ACC_SYNTHETIC 0x1000 方法是否为编译器自动产生

方法中的代码经编译器译为字节码指令后,存放在方法属性表集合的 Code 属性中。

00 0200 0100 0700 0800 0100 09

0x0002 代表集合中共有两个方法,分别为编译器添加的实例构造器 < init > 和 inc() 方法。
以第一个方法为例: ACC_PUBLIC 标志为真,名称索引为 0x0007。描述符索引为 0x0008。属性表计数器为 1。属性名称索引为 0x0009 —— Code,说明此属性为方法的字节码描述,方法中的 java 代码,经编译器编译为字节码文件后,就存放在 Code 属性中。

属性表集合


用于描述某些场景「 专有信息 」,在 Class 文件、字段表、方法表中均可携带自己的属性表。属性表中不要求严格顺序,但一个符合规则的属性表应满足如下结构:

类型 名称 数量 u2 attribute_name_index 1 u2 attribute_length 1 u1 info attribute_length

虚拟机预定义属性:

属性名称 使用位置 含义 Code 方法表 Java代码编译成的字节码指令 ConstantValue 字段表 final关键字定义的常量池 Deprecated 类,方法,字段表 被声明为deprecated的方法和字段 Exceptions 方法表 方法抛出的异常 EnclosingMethod 类文件 仅当一个类为局部类或者匿名类是才能拥有这个属性,这个属性用于标识这个类所在的外围方法 InnerClass 类文件 内部类列表 LineNumberTable Code属性 Java源码的行号与字节码指令的对应关系 LocalVariableTable Code属性 方法的局部便狼描述 StackMapTable Code属性 JDK1.6中新增的属性,供新的类型检查检验器检查和处理目标方法的局部变量和操作数有所需要的类是否匹配 Signature 类,方法表,字段表 用于支持泛型情况下的方法签名 SourceFile 类文件 记录源文件名称 SourceDebugExtension 类文件 用于存储额外的调试信息 Synthetic 类,方法表,字段表 标志方法或字段为编译器自动生成的 LocalVariableTypeTable 类 使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加 RuntimeVisibleAnnotations 类,方法表,字段表 为动态注解提供支持 RuntimeInvisibleAnnotations 表,方法表,字段表 用于指明哪些注解是运行时不可见的 RuntimeVisibleParameterAnnotation 方法表 作用与RuntimeVisibleAnnotations属性类似,只不过作用对象为方法 RuntimeInvisibleParameterAnnotation 方法表 作用与RuntimeInvisibleAnnotations属性类似,作用对象哪个为方法参数 AnnotationDefault 方法表 用于记录注解类元素的默认值 BootstrapMethods 类文件 用于保存invokeddynamic指令引用的引导方式限定符

这里以 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_length u2 attributes_count 1 attribute_info attributes attributes_count
00 0100 0100 0000 052a b700 01b1

0x0001 代表操作数栈深度max_stack为 1。
0x0001 代表局部变量表所需深度max_locals为 1。
0x0005 代表字节码区域所占空间,依次读取 5 个字节,并按照字节码指令表翻译字节码指令。
0x2a —— aload_0,将第 0 个 Slot 中的 reference 类型的本地变量推送至操作数栈顶
0xb7—— invokespecial,以栈顶 reference 数据指向的对象作为方法接收者,调用此对象的实例构造方法,private 方法,或者其他父类方法。此方法存在 u2 类型参数说明调用那个方法。0x000a 代表常量池 < init >。
0xb1 ——对应指令为 return 返回此方法。

0 0
原创粉丝点击