常量池解析(1)


在調用ClassFileParser::parseClassFile()方法對類文件進行解釋時,會調用ClassFileParser::parse_constant_pool()方法對常量池進行解釋,調用的語句如下:

constantPoolHandle cp = parse_constant_pool(CHECK_(nullHandle));

方法parse_constant_pool()的實現如下:

constantPoolHandle ClassFileParser::parse_constant_pool(TRAPS) {
  ClassFileStream* cfs = stream();
  constantPoolHandle nullHandle;

  u2 length = cfs->get_u2_fast();
  ConstantPool* constant_pool = ConstantPool::allocate(_loader_data, length,
                                                        CHECK_(nullHandle));
  _cp = constant_pool; // save in case of errors
  constantPoolHandle cp (THREAD, constant_pool);
  // ...
  // parsing constant pool entries
  parse_constant_pool_entries(length, CHECK_(nullHandle));
  return cp;
}

調用ConstantPool::allocate()創建ConstantPool對象,然后調用parse_constant_pool_entries()解析常量池中的項並將這些項保存到ConstantPool對象中。  

首先介紹一下ConstantPool類,這個類的對象代碼具體的常量池,保存着常量池元信息。

1、ConstantPool類

類的定義如下:

class ConstantPool : public Metadata {
 private:
  Array<u1>*           _tags;        // the tag array describing the constant pool's contents
  ConstantPoolCache*   _cache;       // the cache holding interpreter runtime information 解釋執行時的運行時信息
  InstanceKlass*       _pool_holder; // the corresponding class
  Array<u2>*           _operands;    // for variable-sized (InvokeDynamic) nodes, usually empty

  // 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;

  int                  _flags;  // old fashioned bit twiddling
  int                  _length; // number of elements in the array

  union {
    // set for CDS to restore resolved references
    int                _resolved_reference_length;
    // keeps version number for redefined classes (used in backtrace)
    int                _version;
  } _saved;

  Monitor*             _lock;
  ...
}

類表示常量池元信息,所以繼承了類Metadata。_tags表示常量池中的內容,常量池中的總項數通過_length來保存,所以_tags數組的長度也為_length,具體存儲的內容就是每一項的tag值,這都是虛擬機規范定義好的;_cache輔助解釋運行來保存一些信息,在介紹解釋運行時會介紹。其它的屬性暫時不做過多介紹。

常量池中包含的信息如下:

16535373-7693299f5a4dbc32.png

2、創建ConstantPool實例

在解析常量池的方法ClassFileParser::parse_constant_pool()中首先會調用ConstantPool::allocate()方法創建ConstantPool實例,方法的實現如下:

ConstantPool* ConstantPool::allocate(ClassLoaderData* loader_data, int length, TRAPS) {
  // Tags are RW but comment below applies to tags also.
  Array<u1>* tags = MetadataFactory::new_writeable_array<u1>(loader_data, length, 0, CHECK_NULL);

  int size = ConstantPool::size(length);

  // CDS considerations:
  // Allocate read-write but may be able to move to read-only at dumping time
  // if all the klasses are resolved.  The only other field that is writable is
  // the resolved_references array, which is recreated at startup time.
  // But that could be moved to InstanceKlass (although a pain to access from
  // assembly code).  Maybe it could be moved to the cpCache which is RW.
  return new (loader_data, size, false, MetaspaceObj::ConstantPoolType, THREAD) ConstantPool(tags);
}

參數length就表示常量池項的數量,調用ConstantPool::size()計算所需要分配內存的大小,然后創建ConstantPool對象返回。size()方法的實現如下:

static int size(int length){
	  int s = header_size();
	  return align_object_size(s + length);
}

// Sizing (in words)
static int header_size() {
	  int num = sizeof(ConstantPool);
	  return num/HeapWordSize;
}

由方法實現可知,就是ConstantPool實例本身占用的內存大小加上length個指針長度。ConstantPool對象最終的內存布局如下圖所示。

 _valid是定義在Metadata中的int類型,只有debug版本才有,如果是product版本,則沒有這個屬性,那么Metadata就只占用8字節。關於對象的內存布局在之前已經介紹過,這里不再介紹。

調用header_size()在debug版本下得到的值為88(在不壓縮指針的情況下,也就是使用命令XX禁止指針壓縮),然后還需要加上length個指針寬度,這就是ConstantPool對象需要的內存空間大小。

通過重載new運算符進行堆內存分配,new運算符的重載定義在MetaspaceObj(ConstantPool間接繼承此類)類中,如下:

void* MetaspaceObj::operator new(size_t size, ClassLoaderData* loader_data,
                                 size_t word_size, bool read_only,
                                 MetaspaceObj::Type type, TRAPS) throw() {
  // Klass has it's own operator new
  return Metaspace::allocate(loader_data, word_size, read_only,
                             type, CHECK_NULL);
}

調用的Metaspace::allocate()方法在堆中分配內存,這個方法在介紹垃圾收集時將詳細介紹,這里只需要知道,這個方法會在堆中分配size大小的內存並且會將內存清零。

調用ConstantPool構造函數初始化一些屬性,如下:

ConstantPool::ConstantPool(Array<u1>* tags) {
  set_length(tags->length());
  set_tags(NULL);
  set_cache(NULL);
  set_reference_map(NULL);
  set_resolved_references(NULL);
  set_operands(NULL);
  set_pool_holder(NULL);
  set_flags(0);

  // only set to non-zero if constant pool is merged by RedefineClasses
  set_version(0);
  set_lock(new Monitor(Monitor::nonleaf + 2, "A constant pool lock"));

  // initialize tag array
  int length = tags->length();
  for (int index = 0; index < length; index++) {
    tags->at_put(index, JVM_CONSTANT_Invalid);
  }
  set_tags(tags);
}

可以看到對tags、_length及_lock等屬性的初始化。其中tags數組中存儲了JVM_CONSTANT_Invalid值,在分析具體的常量池項時會更新為如下枚舉類中定義的值:

源代碼位置:hotspot/src/share/vm/prims/jvm.h
enum {
    JVM_CONSTANT_Utf8 = 1,      // 1
    JVM_CONSTANT_Unicode,       // 2      /* unused */
    JVM_CONSTANT_Integer,       // 3
    JVM_CONSTANT_Float,         // 4
    JVM_CONSTANT_Long,          // 5
    JVM_CONSTANT_Double,        // 6
    JVM_CONSTANT_Class,         // 7
    JVM_CONSTANT_String,        // 8
    JVM_CONSTANT_Fieldref,      // 9
    JVM_CONSTANT_Methodref,     // 10
    JVM_CONSTANT_InterfaceMethodref,   // 11
    JVM_CONSTANT_NameAndType,          // 12
    JVM_CONSTANT_MethodHandle           = 15,  // JSR 292
    JVM_CONSTANT_MethodType             = 16,  // JSR 292
    //JVM_CONSTANT_(unused)             = 17,  // JSR 292 early drafts only
    JVM_CONSTANT_InvokeDynamic          = 18,  // JSR 292
    JVM_CONSTANT_ExternalMax            = 18   // Last tag found in classfiles
};

這就是常量池項中的tag值,不過常量池第一項仍然為JVM_CONSTANT_Invalid。

下面介紹一下虛擬機規范規定的格式:

CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

CONSTANT_Integer_info {
    u1 tag;
    u4 bytes;
}

CONSTANT_Float_info {
    u1 tag;
    u4 bytes;
}

CONSTANT_Long_info {
    u1 tag;
    u4 high_bytes;
    u4 low_bytes;
}

CONSTANT_Double_info {
    u1 tag;
    u4 high_bytes;
    u4 low_bytes;
}

CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}


CONSTANT_String_info {
    u1 tag;
    u2 string_index;
}

CONSTANT_Fieldref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

CONSTANT_Methodref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

CONSTANT_InterfaceMethodref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

CONSTANT_NameAndType_info {
    u1 tag;
    u2 name_index;
    u2 descriptor_index;
}


CONSTANT_MethodHandle_info {
    u1 tag;
    u1 reference_kind;
    u2 reference_index;
}

CONSTANT_MethodType_info {
    u1 tag;
    u2 descriptor_index;
}

CONSTANT_InvokeDynamic_info {
    u1 tag;
    u2 bootstrap_method_attr_index;
    u2 name_and_type_index;
}

在常量池解析過程中,通過索引確定了常量池項后會將tag放到ConstantPool類中的_tags數組中,數組的下標與常量池索引相對應;剩下的信息只能存儲到ConstantPool類后開辟的length個指針寬度的空間中,也可以成是length長度的指針數組,其中的下標也與常量池索引對應。指針在64位上的長度為8,所以能夠存儲除CONSTANT_Utf8_info外的所有常量池項信息(除tag外)。例如對於CONSTANT_Double_info來說,高4位存儲high_bytes,低4位存儲low_bytes。遇到CONSTANT_Utf8_info常量池項時,直接封裝為Symbol對象,這樣只要存儲指向Symbol對象的指針即可。

相關文章的鏈接如下:

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文件 

作者持續維護的個人博客classloading.com

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

    

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM