本文我们来介绍ClassFileParser 解析常量池的过程.解析常量池的过程是在ClassFileParser::parseClassFile 通过parse_constant_pool 来实现的.
在parse_constant_pool 中的步骤如下:
在解析常量池之前,已经处理了魔数,读取class文件主,次版本号及验证.在ClassFileParser::parse_constant_pool中获得常量池的长度的代码如下:
ClassFileStream* cfs = stream();
constantPoolHandle nullHandle;
cfs->guarantee_more(3, CHECK_(nullHandle)); // 获得constant_pool_count 和第一个常量池表项的类型
// 1. 获得长度
u2 length = cfs->get_u2_fast();
guarantee_property(
length >= 1, "Illegal constant pool size %u in class file %s",
length, CHECK_(nullHandle)); // 常量池的表项个数必须是大于等于1的
此处回顾一下class文件的格式:
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count; --> 在调用ClassFileParser::parse_constant_pool时ClassFileStream的指针就指向这里
cp_info constant_pool[constant_pool_count-1];
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];
}
其中对于常量池相关项的含义描述如下:
constant_pool_count
常量池计数器,constant_pool_count的值等于constant_pool表中的成员数加1。constant_pool表的索引值只有在大于0且小于constant_pool_count时才会被认为是有效的,对于long和double类型有例外情况。虽然值为0的constant_pool索引是无效的,但其他用到常量池的数据结构可以使用索引0来表示“不引用任何一个常量池项”的意思 该规范说明了constant_pool_count 必须是大于等于1
constant_pool[]
常量池,constant_pool是一种表结构,它包含Class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其它常量。常量池中的每一项都具备相同的格式特征——第一个字节作为类型标记用于识别该项是哪种类型的常量,称为“tag byte”。常量池的索引范围是1至constant_pool_count−1
其中常量池项都具有如下通用格式:
cp_info {
u1 tag;
u1 info[];
}
常量池中,每个cp_info项的格式必须相同,它们都以一个表示cp_info类型的单字节“tag”项开头。后面info[]项的内容tag由的类型所决定。tag有效的类型和对应的取值在表4.3列出。每个tag项必须跟随2个或更多的字节,这些字节用于给定这个常量的信息,附加字节的信息格式由tag的值决定。jvm中可识别的tag如下:
因此,此处获取常量池的长度就可以直接通过读取2个字节后得出(当前得通过字节次序变换).
创建constantPoolOop调用的方法为: oopFactory::new_constantPool.代码如下:
// 位于hotspot/src/share/vm/classfile/classFileParser.cpp
constantPoolOop constant_pool =
oopFactory::new_constantPool(length,
methodOopDesc::IsSafeConc, // 默认为true
CHECK_(nullHandle));
// 位于hotspot/src/share/vm/memory/oopFactory.cpp
constantPoolOop oopFactory::new_constantPool(int length,
bool is_conc_safe,
TRAPS) {
constantPoolKlass* ck = constantPoolKlass::cast(Universe::constantPoolKlassObj());
return ck->allocate(length, is_conc_safe, CHECK_NULL);
}
获取在Universe::genesis(TRAPS)方法中创建的_constantPoolKlassObj.
调用constantPoolKlass::allocate 进行分配.其步骤如下:
调用constantPoolOopDesc::object_size(length) 计算要分配内存的大小
调用CollectedHeap::permanent_obj_allocate进行分配.
初始化constantPoolOop 实例域变量.这里涉及的代码如下:
c->set_length(length);
c->set_tags(NULL);
c->set_cache(NULL);
c->set_operands(NULL);
c->set_pool_holder(NULL);
c->set_flags(0);
// only set to non-zero if constant pool is merged by RedefineClasses
c->set_orig_length(0);
// if constant pool may change during RedefineClasses, it is created
// unsafe for GC concurrent processing.
c->set_is_conc_safe(is_conc_safe);
初始化tag 数组(常量池表项所使用的数组).
其中,constantPoolOopDesc::object_size(length) 的方法如下:
static int header_size() { return sizeof(constantPoolOopDesc)/HeapWordSize; } // 10
static int object_size(int length) { return align_object_size(header_size() + length); }
inline intptr_t align_object_size(intptr_t size) {
return align_size_up(size, MinObjAlignment); // 其中 MinObjAlignment 为2
}
#define align_size_up_(size, alignment) (((size) + ((alignment) - 1)) & ~((alignment) - 1))
inline intptr_t align_size_up(intptr_t size, intptr_t alignment) {
return align_size_up_(size, alignment);
}
总之,其最终返回的大小为: sizeof(constantPoolOopDesc)/HeapWordSize + length 后对齐的结果.
sizeof(constantPoolOopDesc) 的结果为 40,原因如下:
constantPoolOopDesc 继承自oopDesc,其具有如下字段:
volatile markOop _mark;
union _metadata {
wideKlassOop _klass;
narrowOop _compressed_klass;
} _metadata;
// Fast access to barrier set. Must be initialized.
static BarrierSet* _bs; // 该字段不参与计算
因此,在32位的情况下, oopDesc的大小为8.
而在64位的情况下,其大小为如果未开启压缩,则为16,否则为12(_metadata 的大小为4).
而在constantPoolOopDesc 中如下字段:
typeArrayOop _tags; // the tag array describing the constant pool's contents
constantPoolCacheOop _cache; // the cache holding interpreter runtime information
klassOop _pool_holder; // the corresponding class
typeArrayOop _operands; // for variable-sized (InvokeDynamic) nodes, usually empty
int _flags; // a few header bits to describe contents for GC
int _length; // number of elements in the array
volatile bool _is_conc_safe; // if true, safe for concurrent
// GC processing
// only set to non-zero if constant pool is merged by RedefineClasses
int _orig_length;
共8个,其大小为32.
因此,在32位上,sizeof(constantPoolOopDesc)/HeapWordSize + length = 40/4+ length = 10+ length.
其中,调用CollectedHeap::permanent_obj_allocat这一步骤,在类加载流程-002 中有介绍,这里就不在介绍了.不过这里需要补充一点的是,这里为什么分配大小时传入的size为10+ length? 会不会不够用呢?
答案在MutableSpace::allocate中,代码如下:
HeapWord* MutableSpace::allocate(size_t size) {
assert(Heap_lock->owned_by_self() ||
(SafepointSynchronize::is_at_safepoint() &&
Thread::current()->is_VM_thread()),
"not locked");
/*
* 注意,这里先执行HeapWord* obj = top();再执行 HeapWord* new_top = obj + size;,而最终返回的是obj指针,该指针指向的是原来的堆的最顶端,
* 这样,调用方通过指针可以还原从原顶端到当前堆顶之间的内存空间,将其强制转换为常量池对象.
*/
HeapWord* obj = top();
if (pointer_delta(end(), obj) >= size) {
HeapWord* new_top = obj + size; // 将PermSpace内存区域的top指针往高地址移动了size大小的字节数,完成了当前被加载的类所对应的常量池的内存分配
set_top(new_top);
assert(is_object_aligned((intptr_t)obj) && is_object_aligned((intptr_t)new_top),
"checking alignment");
return obj;
} else {
return NULL;
}
}
其中,方法的参数size 是一路传过来的,分配时是通过HeapWord* new_top = obj + size; 进行分配的.这里涉及到了指针运算,因此,其最终的大小为: (10+ length) * 4 . (HeapWord 的大小为4).
接下来,我们介绍一下创建tag 数组(常量池表项所使用的数组)的过程,其代码如下:
constantPoolHandle pool (THREAD, c);
typeArrayOop t_oop = oopFactory::new_permanent_byteArray(length, CHECK_NULL);
typeArrayHandle tags (THREAD, t_oop);
for (int index = 0; index < length; index++) {
tags()->byte_at_put(index, JVM_CONSTANT_Invalid);
}
pool->set_tags(tags());
步骤如下:
首先,调用oopFactory::new_permanent_byteArray 创建typeArrayOopDesc::object_size(layout_helper(), length)大小的数组.
然后将其进行初始化,其值为:JVM_CONSTANT_Invalid
让constantPoolOop 和tag 数组 进行关联.
此时, constantPoolOop的内存结构如下:
这里有个问题,为啥constantPoolOop 为额外分配length个大小的内存,另外又用过tags指向一个typeArrayOop呢? 这个我们在下篇文章中通过介绍ClassFileParser::parse_constant_pool_entries来进行介绍.