专注虚拟机与编译器研究

第7.4篇-类的连接之重写(2)

接着上一篇继续分析Rewriter::Rewriter()构造函数中完成的逻辑。在构造函数中会调用make_constant_pool_cache()函数,不过在先介绍这个函数之前,需要介绍一下ConstantPoolCache与ConstantPoolCacheEntry。这两个类都定义在cpCache.hpp文件中。

1、ConstantPoolCache类

ConstantPoolCache类保存了连接过程中的一些信息,从而让程序在解释执行的过程中避免重复执行连接的过程。这个类的定义如下:

// A constant pool cache is a runtime data structure set aside to a constant pool. The cache
// holds interpreter runtime information for all field access and invoke bytecodes. The cache
// is created and initialized before a class is actively used (i.e., initialized), the individual
// cache entries are filled at resolution (i.e., "link") time (see also: rewriter.*).
class ConstantPoolCache: public MetaspaceObj {
 private:
  int             _length;
  ConstantPool*   _constant_pool;     // the corresponding constant pool

  // Constructor
  ConstantPoolCache(int length,
                    const intStack& inverse_index_map,
                    const intStack& invokedynamic_inverse_index_map,
                    const intStack& invokedynamic_references_map) :
                    _length(length),
                    _constant_pool(NULL) {

    initialize( inverse_index_map,
    		    invokedynamic_inverse_index_map,
			    invokedynamic_references_map);
  }

 private:

  static int header_size() {
	  return sizeof(ConstantPoolCache) / HeapWordSize;   // 2个字,一个字包含有8字节
  }
  static int size(int length) { // 返回的是字数量
	  // ConstantPoolCache加上length个ConstantPoolCacheEntry的大小
	  // in_words(ConstantPoolCacheEntry::size())=4
	  return align_object_size(header_size() + length * in_words(ConstantPoolCacheEntry::size()));
  }
 public:
  int size() const {
	  return size(length());
  }
 private:
  ConstantPoolCacheEntry* base() const {
	  // 这就说明在ConstantPoolCache之后紧接着的是ConstantPoolCacheEntry项
	  return  (ConstantPoolCacheEntry*)(
			        (address)this + in_bytes(base_offset())
			  );
  }

 public:

  // Fetches the entry at the given index.
  // In either case the index must not be encoded or byte-swapped in any way.
  ConstantPoolCacheEntry* entry_at(int i) const {
    assert(0 <= i && i < length(), "index out of bounds");
    return base() + i;
  }

  // Code generation
  static ByteSize base_offset() {
	  return in_ByteSize(sizeof(ConstantPoolCache));
  }
  static ByteSize entry_offset(int raw_index) {
    int index = raw_index;
    return (base_offset() + ConstantPoolCacheEntry::size_in_bytes() * index);
  }

};

如上类删除了一些实现简单或不太重要的方法,保留了属性及重要方法的定义。这个类中定义了2个属性_length及_constant_pool,_length表示,而_constant_pool表示这是保存的哪个常量池连接的信息存,通常缓存具体的信息通过ConstantPoolCacheEntry来表示,它们在内存中的布局就是一个ConstantPoolCache后紧跟着数个ConstantPoolCacheEntry。这样size()及base()等方法的实现就不难简单了。 

ConstantPoolCache主要用于缓存某些字节码指令所需的解析好的常量项,例如给[get|put]static、[get|put]field、invoke[static|special|virtual|interface|dynamic]等指令对应的常量池项使用。

2、ConstantPoolCacheEntry类

 ConstantPoolCacheEntry类及重要属性的定义如下:

class ConstantPoolCacheEntry VALUE_OBJ_CLASS_SPEC {
  private:
    volatile intx        _indices;  // constant pool index & rewrite bytecodes
    volatile Metadata*   _f1;       // entry specific metadata field
    volatile intx        _f2;       // entry specific int/metadata field
    volatile intx        _flags;    // flags
    // ...
}

这4个属性能够表示非常多的信息。这4个字段表示的信息如下图所示。

这4个字段长度相同,以32为操作系统为例来介绍这4个字段。如果当前的ConstantPoolCacheEntry表示的是字段入口,则几个字段的信息如下图所示。

 

如果当前的ConstantPoolCacheEntry表示的是方法入口,则几个字段的信息如下图所示。

  

字节码调用方法的指令主要有如下几个:

(1)invokevirtual,通过vtable进行方法分发

  • _f1:没有使用
  • _f2:调用非final的virtual方法,_f2字段中则存放目标方法在vtable中的索引编号。如果是virtual final方法,_f2字段也直接指向目标方法的Method。

(2)invokeinterface,通过itable进行方法分发

  • _f1:_f1字段指向对应接口的Klass
  • _f2:存放的则是方法位于itable表中的索引编号

(3)invokespecial,调用private和构造方法,不需要分发机制

  • _f1:_f1字段表示指向目标方法Method(用它可以定位Java方法在内存中的具体位置,从而实现方法调用)
  • _f2:没有使用

(4)invokestatic,调用静态方法,不需要分发机制

  • _f1:_f1字段表示指向目标方法Method(用它可以定位Java方法在内存中的具体位置,从而实现方法调用)
  • _f2:没有使用

Note: invokevirtual & invokespecial bytecodes can share the same constant pool entry and thus the same constant pool cache entry. All invoke
bytecodes but invokevirtual use only _f1 and the corresponding b1 bytecode, while invokevirtual uses only _f2 and the corresponding
b2 bytecode. The value of _flags is shared for both types of entries. 

在invokevirtual、invokespecial等字节码指令对应的汇编片段中,如果_indices中的b2或b1不为字节码指令的操作码,说明方法还没有连接,需要调用InterpreterRuntime::resolve_invoke()方法生成ConstantPoolCacheEntry。

之前介绍了重写时调用Rewriter::Rewriter()构造函数,在构造函数中还会调用Rewriter::make_constant_pool_cache()方法,这个方法的实现如下:

// Creates a constant pool cache given a CPC map
void Rewriter::make_constant_pool_cache(TRAPS) {
  InstanceKlass* ik = _pool->pool_holder();
  ClassLoaderData* loader_data = ik->class_loader_data();
  ConstantPoolCache* cache =  ConstantPoolCache::allocate(loader_data,
		                   _cp_cache_map,
                                   _invokedynamic_cp_cache_map,
                                   _invokedynamic_references_map, CHECK);

  // initialize object cache in constant pool
  _pool->initialize_resolved_references(loader_data,
		                        _resolved_references_map,
                                        _resolved_reference_limit,
                                        CHECK);

  _pool->set_cache(cache);           // 设置ConstantPool类中的_cache属性
  cache->set_constant_pool(_pool()); // 设置ConstantPoolCache中的_constant_pool属性
}

调用的ConstantPoolCache::allocate()函数的实现如下:

ConstantPoolCache* ConstantPoolCache::allocate(
		                             ClassLoaderData* loader_data,
                                     const intStack& index_map,
                                     const intStack& invokedynamic_index_map,
                                     const intStack& invokedynamic_map,
									 TRAPS
){
  const int length = index_map.length() + invokedynamic_index_map.length();
  int size = ConstantPoolCache::size(length);

  return new (loader_data, size, false, MetaspaceObj::ConstantPoolCacheType, THREAD)
                  ConstantPoolCache( length,
									  index_map,
									  invokedynamic_index_map,
									  invokedynamic_map);
}

如上方法中调用的ConstantPoolCache::size()函数的实现如下:

static int size(int length) { // 返回的是字数量
   // ConstantPoolCache加上length个ConstantPoolCacheEntry的大小   
   // in_words(ConstantPoolCacheEntry::size()) 的值为4
   return align_object_size(header_size() + length * in_words(ConstantPoolCacheEntry::size()));
}

index_map和invokedynamic_index_map中存储的是常量池索引,这些索引需要建立对应的新的数据结构以表达更多的信息。

调用ConstantPoolCache类的构造函数,如下:  

// Constructor
ConstantPoolCache(int length,
                    const intStack& inverse_index_map,
                    const intStack& invokedynamic_inverse_index_map,
                    const intStack& invokedynamic_references_map) :
                          _length(length),
                          _constant_pool(NULL) {

    initialize( inverse_index_map,
    		    invokedynamic_inverse_index_map,
			    invokedynamic_references_map);
}

void ConstantPoolCache::initialize(const intArray& inverse_index_map,
                                   const intArray& invokedynamic_inverse_index_map,
                                   const intArray& invokedynamic_references_map) {
  for (int i = 0; i < inverse_index_map.length(); i++) {
    ConstantPoolCacheEntry* e = entry_at(i);
    int original_index = inverse_index_map[i];
    e->initialize_entry(original_index); // 为ConstantPoolCacheEntry::_indices属性赋值
    assert(entry_at(i) == e, "sanity");
  }

  // ...
}

void ConstantPoolCacheEntry::initialize_entry(int index) {
  assert(0 < index && index < 0x10000, "sanity check");
  _indices = index;
  _f1 = NULL;
  _f2 = _flags = 0;
  assert(constant_pool_index() == index, "");
}

从inverse_index_map中取出原常量池索引后,存储到_indices中,之前介绍过,_indices的低16位存储原常量池索引,而传递的参数也一定不会超过16位所能表示的最大值。而对于_f1暂时初始化为NULL,_f2与_flags暂时初始化为0,后面还会看到对这些字段的初始化过程。

Rewriter::make_constant_pool_cache()函数中调用的ConstantPool::initialize_resolved_references()函数的实现如下:

// Create resolved_references array and mapping array for original cp indexes
// The ldc bytecode was rewritten to have the resolved reference array index so need a way
// to map it back for resolving and some unlikely miscellaneous uses.
// The objects created by invokedynamic are appended to this list.
void ConstantPool::initialize_resolved_references(
	  ClassLoaderData*    loader_data,
	  intStack            reference_map,
	  int                 constant_pool_map_length,
	  TRAPS
){
  // Initialized the resolved object cache.
  int map_length = reference_map.length();
  if (map_length > 0) {
    // Only need mapping back to constant pool entries.  The map isn't used for
    // invokedynamic resolved_reference entries.  For invokedynamic entries,
    // the constant pool cache index has the mapping back to both the constant
    // pool and to the resolved reference index.
    if (constant_pool_map_length > 0) {
      Array<u2>* om = MetadataFactory::new_array<u2>(loader_data, constant_pool_map_length, CHECK);

      for (int i = 0; i < constant_pool_map_length; i++) {
        int x = reference_map.at(i);
        assert(x == (int)(jushort) x, "klass index is too big");
        om->at_put(i, (jushort)x);
      }
      set_reference_map(om);
    }

    // Create Java array for holding resolved strings, methodHandles,
    // methodTypes, invokedynamic and invokehandle appendix objects, etc.
    objArrayOop   stom = oopFactory::new_objArray(SystemDictionary::Object_klass(), map_length, CHECK);
    Handle        refs_handle(THREAD, (oop)stom);  // must handleize.
    jobject       x = loader_data->add_handle(refs_handle); // 为什么要将refs_handle放到JNIHandleBlock中??
    set_resolved_references(x);
  }
}

为ConstantPool类中的如下属性设置了值:

// Array of resolved objects from the constant pool and map from resolved
// object index to original constant pool index
jobject              _resolved_references; // jobject是指针类型
Array<u2>*           _reference_map;

对于引用来说,这2个属性可完成从以连接的引用索引到原常量池索引的映射,后面会接触到相关应用。这部分内容不太理解也没关系,我们在后面介绍在invokevirtual、invokespecial等字节码指令时,再重新梳理一下逻辑后就明白了。

 

posted on 2020-08-12 09:26  鸠摩(马智)  阅读(746)  评论(0编辑  收藏  举报

导航