InterpreterCodelet依賴CodeletMark完成自動建立和初始化。CodeletMark繼承自ResourceMark,允許自動析構,執行的主要操作就是,會按照InterpreterCodelet中儲存的實際機器指令片段分配記憶體並提交。這個類的定義如下:

class CodeletMark: ResourceMark {
private:
InterpreterCodelet* _clet; // InterpreterCodelet繼承自Stub
InterpreterMacroAssembler** _masm;
CodeBuffer _cb; public:
// 建構函式
CodeletMark(
InterpreterMacroAssembler*& masm,
const char* description,
Bytecodes::Code bytecode = Bytecodes::_illegal):
// AbstractInterpreter::code()獲取的是StubQueue*型別的值,呼叫request()方法獲取的
// 是Stub*型別的值,呼叫的request()方法實現在vm/code/stubs.cpp檔案中
_clet( (InterpreterCodelet*)AbstractInterpreter::code()->request(codelet_size()) ),
_cb(_clet->code_begin(), _clet->code_size())
{ // 初始化InterpreterCodelet中的_description和_bytecode屬性
_clet->initialize(description, bytecode); // InterpreterMacroAssembler->MacroAssembler->Assembler->AbstractAssembler
// 通過傳入的cb.insts屬性的值來初始化AbstractAssembler的_code_section與_oop_recorder屬性的值
// create assembler for code generation
masm = new InterpreterMacroAssembler(&_cb); // 在建構函式中,初始化r13指向bcp、r14指向本地區域性變量表
_masm = &masm;
} // ... 省略解構函式
};

在建構函式中主要完成2個任務:

(1)初始化InterpreterCodelet型別的變數_clet。對InterpreterCodelet例項中的3個屬性賦值;

(2)建立一個InterpreterMacroAssembler例項並賦值給masm與_masm,此例項會通過CodeBuffer向InterpreterCodelet例項寫入機器指令。

在解構函式中,通常在程式碼塊結束時會自動呼叫解構函式,在解構函式中完成InterpreterCodelet使用的記憶體的提交併清理相關變數的值。

1、CodeletMark建構函式

在CodeletMark建構函式會從StubQueue中為InterpreterCodelet分配記憶體並初始化相關變數

在初始化_clet變數時,呼叫AbstractInterpreter::code()方法返回AbstractInterpreter類的_code屬性的值,這個值在之前TemplateInterpreter::initialize()方法中已經初始化了。繼續呼叫StubQueue類中的request()方法,傳遞的就是要求分配的用來儲存code的大小,通過呼叫codelet_size()函式來獲取,如下:

int codelet_size() {
// Request the whole code buffer (minus a little for alignment).
// The commit call below trims it back for each codelet.
int codelet_size = AbstractInterpreter::code()->available_space() - 2*K; return codelet_size;
}

需要注意,在建立InterpreterCodelet時,會將StubQueue中剩下的幾乎所有可用的記憶體都分配給此次的InterpreterCodelet例項,這必然會有很大的浪費,不過我們在解構函式中會按照InterpreterCodelet例項的例項大小提交記憶體的,所以不用擔心浪費這個問題。這麼做的主要原因就是讓各個InterpreterCodelet例項在記憶體中連續存放,這樣有一個非常重要的應用,那就是隻要簡單通過pc判斷就可知道棧幀是否為解釋棧幀了,後面將會詳細介紹。

通過呼叫StubQueue::request()函式從StubQueue中分配記憶體。函式的實現如下:

Stub* StubQueue::request(int  requested_code_size) {

  Stub* s = current_stub();

  int x = stub_code_size_to_size(requested_code_size);
int requested_size = round_to( x , CodeEntryAlignment); // CodeEntryAlignment=32 // 比較需要為新的InterpreterCodelet分配的記憶體和可用記憶體的大小情況
if (requested_size <= available_space()) {
if (is_contiguous()) { // 判斷_queue_begin小於等於_queue_end時,函式返回true
// Queue: |...|XXXXXXX|.............|
// ^0 ^begin ^end ^size = limit
assert(_buffer_limit == _buffer_size, "buffer must be fully usable");
if (_queue_end + requested_size <= _buffer_size) {
// code fits in(適應) at the end => nothing to do
CodeStrings strings;
stub_initialize(s, requested_size, strings);
return s; // 如果夠的話就直接返回
} else {
// stub doesn't fit in at the queue end
// => reduce buffer limit & wrap around
assert(!is_empty(), "just checkin'");
_buffer_limit = _queue_end;
_queue_end = 0;
}
}
} // ... return NULL;
}

通過如上的函式,我們能夠清楚看到如何從StubQueue中分配InterpreterCodelet記憶體的邏輯。

首先計算此次需要從StubQueue中分配的記憶體大小,呼叫的相關函式如下:

呼叫的stub_code_size_to_size()函式的實現如下:

// StubQueue類中定義的函式
int stub_code_size_to_size(int code_size) const {
return _stub_interface->code_size_to_size(code_size);
} // InterpreterCodeletInterface類中定義的函式
virtual int code_size_to_size(int code_size) const {
return InterpreterCodelet::code_size_to_size(code_size);
} // InterpreterCodelet類中定義的函式
static int code_size_to_size(int code_size) {
// CodeEntryAlignment = 32
// sizeof(InterpreterCodelet) = 32
return round_to(sizeof(InterpreterCodelet), CodeEntryAlignment) + code_size;
}

通過如上的分配記憶體大小的方式可知記憶體結構如下:

在StubQueue::request()函式中計算出需要從StubQueue中分配的記憶體大小後,下面進行記憶體分配。StubQueue::request()函式只給出了最一般的情況,也就是假設所有的InterpreterCodelet例項都是從StubQueue的_stub_buffer地址開始連續分配的。is_contiguous()函式用來判斷區域是否連續,實現如下:

bool is_contiguous() const {
return _queue_begin <= _queue_end;

呼叫的available_space()函式得到StubQueue可用區域的大小,實現如下:

// StubQueue類中定義的方法
int available_space() const {
int d = _queue_begin - _queue_end - 1;
return d < 0 ? d + _buffer_size : d;

呼叫如上函式後得到的大小為下圖的黃色區域部分。

繼續看StubQueue::request()函式,當能滿足此次InterpreterCodelet例項要求的記憶體大小時,會呼叫stub_initialize()函式,此函式的實現如下:

// 下面都是通過stubInterface來操作Stub的
void stub_initialize(Stub* s, int size,CodeStrings& strings) {
// 通過_stub_interface來操作Stub,會呼叫s的initialize()函式
_stub_interface->initialize(s, size, strings);
} // 定義在InterpreterCodeletInterface類中函式
virtual void initialize(Stub* self, int size,CodeStrings& strings){
cast(self)->initialize(size, strings);
}   // 定義在InterpreterCodelet類中的函式
void initialize(int size,CodeStrings& strings) {
_size = size;
}

我們通過StubInterface類中定義的函式來操作Stub,至於為什麼要通過StubInterface來操作Stub,就是因為Stub例項很多,所以為了避免在Stub中寫虛擬函式(C++中對含有虛擬函式的類需要分配一個指標的空間指向虛擬函式表)浪費記憶體空間而採取的辦法。

如上3個函式最終只完成了一件事兒,就是將此次分配到的記憶體大小記錄在InterpreterCodelet的_size屬性中。前面在介紹函式codelet_size()時提到過,這個值在儲存了機器指令片段後通常還會空餘很多空間,不過不要著急,下面要介紹的解構函式會根據InterpreterCodelet例項中實際生成的機器指令的大小更新這個屬性值。

2、CodeletMark解構函式

解構函式的實現如下:

// 解構函式
~CodeletMark() {
// 對齊InterpreterCodelet
(*_masm)->align(wordSize); // 確保生成的所有機器指令片段都儲存到了InterpreterCodelet例項中
(*_masm)->flush(); // 更新InterpreterCodelet例項的相關屬性值
AbstractInterpreter::code()->commit((*_masm)->code()->pure_insts_size(), (*_masm)->code()->strings()); // 設定_masm,這樣就無法通過這個值繼續向此InterpreterCodelet例項中生成機器指令了
*_masm = NULL;
}

呼叫AbstractInterpreter::code()函式獲取StubQueue。呼叫(*_masm)->code()->pure_insts_size()獲取的就是InterpreterCodelet例項的機器指令片段實際需要的記憶體大小。

StubQueue::commit()函式的實現如下:  

void StubQueue::commit(int committed_code_size, CodeStrings& strings) {
int x = stub_code_size_to_size(committed_code_size);
int committed_size = round_to(x, CodeEntryAlignment); Stub* s = current_stub();
assert(committed_size <= stub_size(s), "committed size must not exceed requested size"); stub_initialize(s, committed_size, strings);
_queue_end += committed_size;
_number_of_stubs++;
}

呼叫stub_initialize()函式通過InterpreterCodelet例項的_size屬性記錄此例項中機器指令片段實際記憶體大小。同時更新StubQueue的_queue_end和_number_of_stubs屬性的值,這樣就可以為下次InterpreterCodelet例項繼續分配記憶體了。

推薦閱讀:

第1篇-關於JVM執行時,開篇說的簡單些

第2篇-JVM虛擬機器這樣來呼叫Java主類的main()方法

第3篇-CallStub新棧幀的建立

第4篇-JVM終於開始呼叫Java主類的main()方法啦

第5篇-呼叫Java方法後彈出棧幀及處理返回結果

第6篇-Java方法新棧幀的建立

第7篇-為Java方法建立棧幀

第8篇-dispatch_next()函式分派位元組碼

第9篇-位元組碼指令的定義

第10篇-初始化模板表

第11篇-認識Stub與StubQueue

如果有問題可直接評論留言或加作者微信mazhimazh

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