JVM-解析常量池
Java最显著的特点就是"Write Once, Run Anywhere", 这全是因为虚拟机JVM的存在,使得Java代码的运行可以不受操作系统的限制。不论是Java语言的代码还是其他语言的代码,最终都可以编译成字节码.Class文件,虚拟机并不关心文件来自什么语言,只要符合Class文件的格式,可以在虚拟机中运行就行。
Class文件中只要两种数据机构:无符号数和表;无符号数u1,u2,u4,u8分别代表1个字节,2个字节,4个字节和8个字节;表是由多个无符号数或其他表构成的复合数据类型,和C语言中的结构体类似。整个Class文件就是由以下的数据项构成的:
类型 | 名称 | 数量 |
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 | attribute_count | 1 |
attribute_info | attributes | attributes_count |
下面是十六进制编辑器wxmedit打开的一个Class文件
前四个字节CA FE BA BE 称为魔数(Magic Number)对应u4类型的magic,魔数是用来判定当前的文件是否为Class文件
第五和第六个字节 00 00 代表次版本号(Minor Version),第七和第八个字节00 34代表主版本号(Major Version)52,对应JDK52.0
主版本号之后就是常量池(Constant Pool)的入口,是占用Class文件最大的项目,常量池的大小是不固定的,所以在常量池入口处首先看到的是常量池的计数值(constant_pool_count)00 36 ,但常量池的容量不是从0而是从1开始计数的,所以对应常量池有54-1=53个常量项
常量池中主要存放两大类常量:字面量和符号引用;字面量好比Java中的文本字符串和被声明的final常量,符号引用主要包括了三类常量:类和接口的全限定名,字段的名称和描述符,方法的名称和描述符
之所以存在常量池是因为Java代码在编译的时候没有"连接"
C 语言: 编译->链接->.exe->执行
函数A调用函数B,在链接时会直接在函数A中记录函数B的地址。
Java : 编译->.class ->装载执行
类 Employee 中使用了另外一个类 Department,在Employee.class 中只保存类Department的名称, 而不会保留类Department的“地址”。虚拟机运行的时候需要从常量池获得对应的符号引用,在类创建的时候翻译并解析到具体的内存之中。
下面是常量池中包含的项目类型
常量池中数据项类型 | 类型标志 | 类型描述 |
CONSTANT_Utf8 | 1 | UTF-8编码的Unicode字符串 |
CONSTANT_Integer | 3 | int类型字面值 |
CONSTANT_Float | 4 | float类型字面值 |
CONSTANT_Long | 5 | long类型字面值 |
CONSTANT_Double | 6 | double类型字面值 |
CONSTANT_Class | 7 | 对一个类或接口的符号引用 |
CONSTANT_String | 8 | String类型字面值 |
CONSTANT_Fieldref | 9 | 对一个字段的符号引用 |
CONSTANT_Methodref | 10 | 对一个类中声明的方法的符号引用 |
CONSTANT_InterfaceMethodref | 11 | 对一个接口中声明的方法的符号引用 |
CONSTANT_NameAndType | 12 | 对一个字段或方法的部分符号引用 |
常量池的第一项常量,标志位tag = 07,对应 CONSTANT_Class 类型
类型 | 名称 | 数量 |
u1 | tag | 1 |
u2 | name_index | 1 |
标志位之后是name_index = 00 02,指向常量池中的第二项, tag = 01,表示CONSTANT_Utf8
类型 | 名称 | 数量 |
u1 | tag | 1 |
u2 | length | 1 |
u1 | bytes | length |
第二项常量中 00 33 十进制表示51,即length=51,即此UTF8编码的字符串长度是51个字节
第三项常量tag = 07, 可知是CONSTANT_Class 类型 ,name_index = 00 04, 指向第四项常量
第四项常量tag = 01, 可知是CONSTANT_Utf8类型,length = 16 (00 10)转换而来
其他项的解析和之前的类似,对照具体的常量的结构来分析
常量 |
项目 |
类型 |
描述 |
CONSTANT_Utf8 |
tag |
u1 |
值为1 |
length |
u2 |
UF-8编码的字符串占用的字节数 |
|
bytes |
u1 |
长度为length的UTF-8编码的字符串 |
|
CONSTANT_Integer |
tag |
u1 |
值为3 |
bytes |
u4 |
按照高位在前存储的int值 |
|
CONSTANT_Float |
tag |
u1 |
值为4 |
bytes |
u4 |
按照高位在前存储的float值 |
|
CONSTANT_Long |
tag |
u1 |
值为5 |
bytes |
u8 |
按照高位在前存储的long值 |
|
CONSTANT_Double |
tag |
u1 |
值为6 |
bytes |
u8 |
按照高位在前存储的double值 |
|
CONSTANT_Class |
tag |
u1 |
值为7 |
index |
u2 |
指向全限定名常量项的索引 |
|
CONSTANT_String |
tag |
u1 |
值为8 |
index |
u2 |
指向字符串字面量的索引 |
|
CONSTANT_Fieldref |
tag |
u1 |
值为9 |
index |
u2 |
指向声明字段的类或接口描述符CONSTANT_Class_info的索引项 |
|
index |
u2 |
指向字段名称及类型描述符CONSTANT_NameAndType_info的索引项 |
|
CONSTANT_Methodref |
tag |
u1 |
值为10 |
index |
u2 |
指向声明方法的类描述符CONSTANT_Class_info的索引项 |
|
index |
u2 |
指向方法名称及类型描述符CONSTANT_NameAndType_info的索引项 |
|
CONSTANT_InrerfaceMethodref |
tag |
u1 |
值为11 |
index |
u2 |
指向声明方法的接口描述符CONSTANT_Class_info的索引项 |
|
index |
u2 |
指向方法名称及类型描述符CONSTANT_NameAndType_info的索引项 |
|
CONSTANT_NameAndType |
tag |
u1 |
值为12 |
index |
u2 |
指向字段或方法名称常量项目的索引 |
|
index |
u2 |
指向该字段或方法描述符常量项的索引 |