第12篇-認識CodeletMark


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源碼剖析系列文章!

 

 

  

 


免責聲明!

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



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