Method中的一個重要字段為_intrinsic_id,為了追求極致的性能,將這些方法叫固有方法(Intrinsic Method)。所有的固有方法都能在classfile/vmSymbols.hpp中找到,一個絕佳的例子是java.lang.Math。對於Math.sqrt(),用Java或者JNI均無法達到極致性能,這時可以將其置為固有方法,當虛擬機遇到它時只需要一條CPU指令fsqrt,用硬件級實現碾壓軟件級算法。
HotSpot通過Method與ConstMethod來保存方法元信息。
1、Method
Method沒有子類,定義在method.hpp文件中,其類繼承關系如下圖:
Method用於表示一個Java方法,因為一個應用有成千上萬個方法,因此保證Method類在內存中短小非常有必要。為了本地GC方便,Method把所有的指針變量和方法大小放在Method內存布局的前面。
方法本身的不可變數據,如字節碼用ConstMethod表示,可變數據如Profile統計的性能數據等用MethodData表示,都通過指針訪問。
如果是本地方法,Method內存結構的最后是native_function和signature_handler屬性,按照解釋器的要求,這兩個必須在固定的偏移處。
Method類中聲明的屬性如下:
源代碼位置:/vm/oops/method.hpp
class Method : public Metadata {
friend class VMStructs;
private:
ConstMethod* _constMethod; // Method read-only data.
// MethodData結構基礎是ProfileData,記錄函數運行狀態下的數據
// MethodData里面分為3個部分,一個是函數類型等運行相關統計數據,一個是參數類型運行相關統計數據,
// 還有一個是extra擴展區保存着deoptimization的相關信息
MethodData* _method_data;
MethodCounters* _method_counters;
AccessFlags _access_flags; // Access flags
// vtable index of this method (see VtableIndexFlag)
// note: can have vtables with >2**16 elements (because of inheritance)
int _vtable_index;
u2 _method_size; // size of this object
u1 _intrinsic_id; // vmSymbols::intrinsic_id (0 == _none)
// Flags,在定義時指定各個變量占用的位
u1 _jfr_towrite : 1, // Flags
_caller_sensitive : 1,
_force_inline : 1,
_hidden : 1,
_dont_inline : 1,
: 3;
// Entry point for calling both from and to the interpreter.
address _i2i_entry; // All-args-on-stack calling convention
/*
_adapter 指向該Java方法的簽名(signature)所對應的 i2c2i adapter stub。其實是一個 i2c stub
和一個 c2i stub 粘在一起這樣的對象,可以看到用的時候都是從 _adapter 取 get_i2c_entry() 或
get_c2i_entry()。這些adapter stub用於在HotSpot VM里的解釋模式與編譯模式的代碼之間適配其
calling convention。HotSpot VM里的解釋模式calling convention用棧來傳遞參數,而編譯模式的
calling convention更多采用寄存器來傳遞參數,兩者不兼容,因而從解釋模式的代碼調用已經被編譯的方法,
或者反之,都需要在調用時進行適配。
*/
// Adapter blob (i2c/c2i) for this Method*. Set once when method is linked.
AdapterHandlerEntry* _adapter;
/*
_from_compiled_entry 初始值指向c2i adapter stub。原因上面已經說了,因為一開始該方法尚未被JIT編譯,
需要在解釋模式執行,那么從已經JIT編譯好的Java方法調用過來的話就需要進行calling convention的轉換,
把參數挪到正確的位置上。當該方法被JIT編譯並“安裝”完之后,_from_compiled_entry 就會指向編譯出來的機
器碼的入口,具體說時指向verified entry point。如果要拋棄之前編譯好的機器碼,那么 _from_compiled_entry
會恢復為指向 c2i stub。
*/
// Entry point for calling from compiled code, to compiled code if it exists
// or else the interpreter.
// Cache of: _code ? _code->entry_point() : _adapter->c2i_entry()
volatile address _from_compiled_entry;
// The entry point for calling both from and to compiled code is
// "_code->entry_point()". Because of tiered compilation and de-opt, this
// field can come and go. It can transition from NULL to not-null at any
// time (whenever a compile completes). It can transition from not-null to
// NULL only at safepoints (because of a de-opt).
// nmethod全名native method,指向的是Java method編譯的一個版本。
// 當一個方法被JIT編譯后會生成一個nmethod,指向的是編譯的代碼
// _code的類型為nmethod
nmethod* volatile _code; // Points to the corresponding piece of native code
/*
_from_interpreted_entry 初始的值與 _i2i_entry 一樣。但后面當該Java方法被JIT編譯並“安裝”之后,
_from_interpreted_entry 就會被設置為指向 i2c adapter stub。而如果因為某些原因需要拋棄掉之前已
經編譯並安裝好的機器碼,則 _from_interpreted_entry 會被恢復為 _i2i_entry。
*/
// Cache of _code ? _adapter->i2c_entry() : _i2i_entry
volatile address _from_interpreted_entry; // 如果有_code,則通過i2c_entry轉向編譯方法,否則通過_i2i_entry轉向解釋方法
...
}
HotSpot虛擬機中的Method存儲在元數據區(在JDK1.8之前是PermGen)。
Method中最后定義的幾個屬性非常重要。用以方法的解釋執行和編譯執行。一個方法可能有多個入口:
1、_i2i_entry :指向字節碼解釋執行的入口;
2、_code->entry_point() :指向JIT編譯代碼的入口。編譯后的代碼存儲在CodeCache中,這是專門為動態生成的代碼開辟的一塊本地內存;
3、i2c和c2i適配器:用來在解釋執行和編譯執行之間進行轉換,由於解釋執行和編譯執行的調用約定不同,所以專門做了適配器來適配。
可以這樣總結一下,如果轉換的目標是解析執行,那么:
- from_compiled_code_entry_point 的值為 c2i adapter
- from_interpreter_entry_point 的值為 interpreter entry point
如果轉換的目標是編譯執行:
- from_compiled_code_entry_point 的值為 nmethod entry point
- from_interpreter_entry_point 的值為 i2c adapter
Method類中定義的各屬性的介紹如下表所示。
| 字段名 | 描述 |
| _constMethod | ConstMethod指針,該類定義在constMethod.hpp文件中,用於表示方法的不可變的部分,如方法ID, 方法的字節碼大小,方法名在常量池中的索引等 |
| _method_data | MethodData指針,該類在methodData.hpp中定義,用於表示一個方法在執行期間收集的相關信息, 如方法的調用次數,在C1編譯期間代碼循環和阻塞的次數,Profile收集的方法性能相關的數據等 |
| _method_counters | MethodCounters指針,該類在methodCounters.hpp中定義,用於大量編譯優化相關的計數,
主要用於基於調用頻率的熱點方法的跟蹤統計 |
| _access_flags | AccessFlags類,表示方法的訪問控制標識 |
| _vtable_index | 該方法在vtable表中的索引 |
| _method_size | 這個Method對象的大小,以字寬為單位 |
| _intrinsic_id | 固有方法(intrinsic method)在虛擬機中表示一些眾所周知的方法,針對它們可以做特別處理,生成獨特的代碼例程。 虛擬機發現一個方法是固有方法就不會走逐行解釋字節碼這條路徑而是跳到獨特的代碼例程上面, 所有的固有方法都定義在 |
| _compiled_invocation_count | 編譯后的方法叫nmethod,這個就是用來計數編譯后的nmethod調用了多少次,如果該方法是解釋執行就為0 |
| _i2i_entry | 解釋器的入口地址 |
| _adapter | 此方法在解釋器和編譯器執行的適配器 |
| _from_compiled_entry | 執行編譯后的代碼的入口地址 |
| _code | nmethod類指針,表示該方法編譯后的本地代碼 |
| _from_interpreted_entry | code ? _adapter->i2c_entry() : _i2i_entry的緩存 |
基中_access_flags的取值如下:

_vtable_index的取值如下:
2、ConstMethod類介紹
ConstMethod對象代表方法中不可變的部分,例如字節碼。類的定義如下:
源代碼位置:/hotspot/src/share/vm/oop
class ConstMethod : public MetaspaceObj {
...
private:
...
// 其中_constants,_method_idnum這兩個是連接Method的參數,因為Method有ConstMethod指針,
// 但ConstMethod沒有Method的指針,需要通過如下步驟來查找:
// ConstantPool -> InstanceKlass -> Method數組->通過_method_idnum獲取對應的Method指針
// 原文鏈接:https://blog.csdn.net/raintungli/article/details/83857136
ConstantPool* _constants; // Constant pool
int _constMethod_size;
u2 _flags;
// Size of Java bytecodes allocated immediately after Method*.
u2 _code_size;
u2 _name_index; // Method name (index in constant pool)
u2 _signature_index; // Method signature (index in constant pool)
u2 _method_idnum; // unique identification number for the method within the class
// initially corresponds to the index into the methods array.
// but this may change with redefinition
u2 _max_stack; // Maximum number of entries on the expression stack
u2 _max_locals; // Number of local variables used by this method
u2 _size_of_parameters;// size of the parameter block (receiver + arguments) in words
...
}
該類用於表示方法的不可變部分,如方法的id(_method_idnum),方法的字節碼大小,方法名在常量池中的索引等,其中_constMethod_size的大小以字寬為單位,_code_size的大小以字節為單位,其內存結構如下圖,因為異常檢查表平均長度小於2,本地變量表大多數情況下沒有,所以這兩個沒有被壓縮保存。訪問這些內嵌表都很快,不是性能瓶頸。ConstMethod提供了獲取內嵌部分,如字節碼的起始地址,然后可以據此偏移地址獲取的方法字節碼。
相關文章的鏈接如下:
1、在Ubuntu 16.04上編譯OpenJDK8的源代碼
13、類加載器
14、類的雙親委派機制
15、核心類的預裝載
16、Java主類的裝載
17、觸發類的裝載
18、類文件介紹
19、文件流
20、解析Class文件
21、常量池解析(1)
22、常量池解析(2)
23、字段解析(1)
24、字段解析之偽共享(2)
25、字段解析(3)
作者持續維護的個人博客classloading.com。
關注公眾號,有HotSpot源碼剖析系列文章!
參考文章:
