一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情。
Class文件的逻辑结构
文件头
| 类型 | 名称 | 数量 |
|---|---|---|
| u4 | 魔数 | 1 |
| u2 | 次版本号 | 1 |
| u2 | 主版本号 | 1 |
魔数的唯一作用是告诉虚拟机,这是一个可接受的class文件,固定值为0xCAFEBABE
常量池
| 类型 | 名称 | 数量 |
|---|---|---|
| u2 | 常量池计数 | 1 |
| cp_info | 常量 | 常量池计数 -1 |
常量池就是class文件的资源仓库,占用空间较大。
常量池容器的计数从1开始,所以常量的数量是计数-1
cp_info能容纳哪些内容
- 字面量
- 字符串文本
- final常量的值
- 基本数据类型的值
- 符号
- 类和接口的完全限定名
- 字段名和类型
- 方法名和类型
cp_info 的结构
struct cp_info {
u1 tag;//类型
u1 info[]; //数据
}
| tag | 表示字面量 | 细分结构 |
|---|---|---|
| 1 | 字符串 | Utf8_info |
| 3 | 4字节数值(int) | Integer_info |
| 4 | 4字节数值(float) | Float_info |
| 5 | 8字节数值(float) | Long_info |
| 6 | 8字节数值(double) | Double_info |
| 7 | 类和接口的完全限定名 | Class_info |
| 8 | java.lang.String类型的常量实例 | String_info |
| 9 | 字段 | Fieldref_info |
| 10 | 方法 | Methodref_info |
| 11 | 类实现的接口 | InterfaceMethodref_info |
| 12 | 字段或方法的名称及类型 | NameAndType_info |
| 15 | 方法句柄 | MethodHandle_info |
| 16 | 方法类型 | MethodType_info |
| 18 | 动态调用 | InvokeDynamic_info |
常量池中的字符串
struct CONSTANT_String_info{
u1 tag = 8; //固定取值8
u2 string_index;//指向某个Utf8_info在常量池中的索引
}
struct CONSTANT_Utf8_info{
u1 tag = 1; //固定取值1
u2 length;//Utf8编码的字节数组的长度
u1 bytes[length];//Utf8编码的字节数组
}
类信息
| 类型 | 名称 | 数量 |
|---|---|---|
| u2 | 访问修饰限定符 | 1 |
| u2 | this class | 1 |
| u2 | super class | 1 |
| u2 | 接口计数 | 1 |
| u2 | 接口 | 接口计数 |
字段表
| 类型 | 名称 | 数量 |
|---|---|---|
| u2 | 字段表计数 | 1 |
| field_info | 字段 | 字段表计数 |
字段包括类级别和实例级别的变量,不包括方法内部的局部变量。
方法表(存放方法描述)
| 类型 | 名称 | 数量 |
|---|---|---|
| u2 | 方法表计数 | 1 |
| method_info | 字段 | 方法表计数 |
属性信息
结构
- 属性名:属性值
dex文件
class文件和dex的区别
dex文件的文件头
struct DexHeader{
u1 magic[8];//魔数和版本号
u4 checksum;//adler32算法校验码
u1 signature[kSHA1DigestLen];//文件内容签名
u4 fileSize;//文件总大小
u4 headerSize;//文件头大小
u4 endianTag;//字节序
u4 linkSize;
u4 linkOff;
u4 mapOff;
}
dex文件的索引区
struct DexStringId{
u4 stringDataOff;//string_data_item的文件偏移量
}
struct DexTypeId{
u4 descriptorIdx;//指向dexStringId的索引
}
struct DexFieldId{
u2 classIdx;//所在类,指向dexTypeId的索引
u2 typeIdx;//类型,指向dexTypeId的索引
u4 nameIdx;//字段名,指向dexStringId的索引
}
struct DexMethodId{
u2 classIdx;//索引到typeIds,表示所在类
u2 protoIdx;//索引到protoIds,表示方法原型
u4 nameIdx;//索引到stringIds,表示方法名
}
struct DexProtoId{
u2 shortyIdx;//索引到stringIds,表示短描述符
u2 returnTypeIdx;//索引到typeIds,表示返回值类型
u4 parametersOff;//type_list中的偏移量,参数类型
}
索引区&数据区
class文件 vs dex文件
class文件
- 每个类都有独立的常量池,导致代码冗余
- 每个类属于一个文件
- 基于栈的字节码
dex文件
- 常量记录在数据区,所有类都通过索引获取常量的值,相同的常量可以合并,大大降低类代码的冗余。节约移动端安装包的体积。
- 所有类在同一个文件中,即使是multdex,文件总数也远小于class文件(降低I/0操作开销)
- 基于虚拟寄存器的字节码(节约内存)