1. 程式人生 > 實用技巧 >類的連線之重寫(2)

類的連線之重寫(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 constantpool entry and thus the same constant pool cache entry. All invoke
bytecodes but invokevirtual use only _f1 and the corresponding b1bytecode, while invokevirtual uses only _f2 and the corresponding
b2 bytecode. The value of _flags is shared for both types of entries.

之前介紹了重寫時呼叫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);
        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);
    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等位元組碼指令時,再重新梳理一下邏輯後就明白了。

相關文章的連結如下:

1、在Ubuntu 16.04上編譯OpenJDK8的原始碼

2、除錯HotSpot原始碼

3、HotSpot專案結構 

4、HotSpot的啟動過程

5、HotSpot二分模型(1)

6、HotSpot的類模型(2)

7、HotSpot的類模型(3)

8、HotSpot的類模型(4)

9、HotSpot的物件模型(5)

10、HotSpot的物件模型(6)

11、操作控制代碼Handle(7)

12、控制代碼Handle的釋放(8)

13、類載入器

14、類的雙親委派機制

15、核心類的預裝載

16、Java主類的裝載

17、觸發類的裝載

18、類檔案介紹

19、檔案流

20、解析Class檔案

21、常量池解析(1)

22、常量池解析(2)

23、欄位解析(1)

24、欄位解析之偽共享(2)

25、欄位解析(3)

26、欄位解析之OopMapBlock(4)

27、方法解析之Method與ConstMethod介紹

28、方法解析

29、klassVtable與klassItable類的介紹

30、計算vtable的大小

31、計算itable的大小

32、解析Class檔案之建立InstanceKlass物件

33、欄位解析之欄位注入

34、類的連線

35、類的連線之驗證

36、類的連線之重寫(1)

作者持續維護的個人部落格classloading.com

關注公眾號,有HotSpot原始碼剖析系列文章!