类文件解析003-解析常量池

本文我们来介绍ClassFileParser 解析常量池的过程.解析常量池的过程是在ClassFileParser::parseClassFile 通过parse_constant_pool 来实现的.

在parse_constant_pool 中的步骤如下:

  1. 获得常量池的长度
  2. 创建constantPoolOop
  3. 调用parse_constant_pool_entries 解析常量池操作
  4. 验证交叉引用和修复类和字符串常量
  5. 检查string是否是正确的格式

获取长度

在解析常量池之前,已经处理了魔数,读取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如下:

    类文件解析003-解析常量池_第1张图片

因此,此处获取常量池的长度就可以直接通过读取2个字节后得出(当前得通过字节次序变换).


创建constantPoolOop

创建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);
}

  1. 获取在Universe::genesis(TRAPS)方法中创建的_constantPoolKlassObj.

  2. 调用constantPoolKlass::allocate 进行分配.其步骤如下:

    1. 调用constantPoolOopDesc::object_size(length) 计算要分配内存的大小

    2. 调用CollectedHeap::permanent_obj_allocate进行分配.

    3. 初始化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);
      
    4. 初始化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());

步骤如下:

  1. 首先,调用oopFactory::new_permanent_byteArray 创建typeArrayOopDesc::object_size(layout_helper(), length)大小的数组.

  2. 然后将其进行初始化,其值为:JVM_CONSTANT_Invalid

  3. 让constantPoolOop 和tag 数组 进行关联.

此时, constantPoolOop的内存结构如下:

类文件解析003-解析常量池_第2张图片

这里有个问题,为啥constantPoolOop 为额外分配length个大小的内存,另外又用过tags指向一个typeArrayOop呢? 这个我们在下篇文章中通过介绍ClassFileParser::parse_constant_pool_entries来进行介绍.

你可能感兴趣的:(openjdk,openjdk)