天天看點

第20篇-加載與存儲指令之ldc與_fast_aldc指令(2)

第20篇-加載與存儲指令之ldc與_fast_aldc指令(2)

ldc指令将int、float、或者一個類、方法類型或方法句柄的符号引用、還可能是String型常量值從常量池中推送至棧頂。

這一篇介紹一個虛拟機規範中定義的一個位元組碼指令ldc,另外還有一個虛拟機内部使用的位元組碼指令_fast_aldc。ldc指令可以加載String、方法類型或方法句柄的符号引用,但是如果要加載String、方法類型或方法句柄的符号引用,則會在類連接配接過程中重寫ldc位元組碼指令為虛拟機内部使用的位元組碼指令_fast_aldc。下面我們詳細介紹ldc指令如何加載int、float類型和類類型的資料,以及_fast_aldc加載String、方法類型或方法句柄,還有為什麼要進行位元組碼重寫等問題。

1、ldc位元組碼指令

ldc指令将int、float或String型常量值從常量池中推送至棧頂。模闆的定義如下:

def(Bytecodes::_ldc , ubcp|____|clvm|____, vtos, vtos, ldc ,  false );      

ldc位元組碼指令的格式如下:

// index是一個無符号的byte類型資料,指明目前類的運作時常量池的索引
ldc index       

調用生成函數TemplateTable::ldc(bool wide)。函數生成的彙編代碼如下:  

第1部分代碼:

// movzbl指令負責拷貝一個位元組,并用0填充其目
// 的操作數中的其餘各位,這種擴充方式叫"零擴充"
// ldc指定的格式為ldc index,index為一個位元組
0x00007fffe1028530: movzbl 0x1(%r13),%ebx // 加載index到%ebx

// %rcx指向緩存池首位址、%rax指向類型數組_tags首位址
0x00007fffe1028535: mov    -0x18(%rbp),%rcx
0x00007fffe1028539: mov    0x10(%rcx),%rcx
0x00007fffe102853d: mov    0x8(%rcx),%rcx
0x00007fffe1028541: mov    0x10(%rcx),%rax


// 從_tags數組擷取操作數類型并存儲到%edx中
0x00007fffe1028545: movzbl 0x4(%rax,%rbx,1),%edx

// $0x64代表JVM_CONSTANT_UnresolvedClass,比較,如果類還沒有連結,
// 則直接跳轉到call_ldc
0x00007fffe102854a: cmp    $0x64,%edx
0x00007fffe102854d: je     0x00007fffe102855d   // call_ldc

// $0x67代表JVM_CONSTANT_UnresolvedClassInError,也就是如果類在
// 連結過程中出現錯誤,則跳轉到call_ldc
0x00007fffe102854f: cmp    $0x67,%edx
0x00007fffe1028552: je     0x00007fffe102855d  // call_ldc

// $0x7代表JVM_CONSTANT_Class,表示如果類已經進行了連接配接,則
// 跳轉到notClass
0x00007fffe1028554: cmp    $0x7,%edx
0x00007fffe1028557: jne    0x00007fffe10287c0  // notClass

// 類在沒有連接配接或連接配接過程中出錯,則執行如下的彙編代碼
// -- call_ldc --      

下面看一下調用call_VM(rax, CAST_FROM_FN_PTR(address, InterpreterRuntime::ldc), c_rarg1)函數生成的彙編代碼,CAST_FROM_FN_PTR是宏,宏擴充後為( (address)((address_word)(InterpreterRuntime::ldc)) )。

在調用call_VM()函數時,傳遞的參數如下:

  • %rax現在存儲類型數組首位址,不過傳入是為了接收調用函數的結果值
  • adr是InterpreterRuntime::ldc()函數首位址
  • c_rarg1用rdi寄存器存儲wide值,這裡為0,表示為沒有加wide字首的ldc指令生成彙編代碼

生成的彙編代碼如下:

第2部分:

// 将wide的值移到%esi寄存器,為後續
// 調用InterpreterRuntime::ldc()函數準備第2個參數
0x00007fffe102855d: mov $0x0,%esi 
// 調用MacroAssembler::call_VM()函數,通過此函數來調用HotSpot VM中用
// C++編寫的函數,通過這個C++編寫的函數來調用InterpreterRuntime::ldc()函數

0x00007fffe1017542: callq  0x00007fffe101754c 
0x00007fffe1017547: jmpq   0x00007fffe10175df // 跳轉到E1

// 調用MacroAssembler::call_VM_helper()函數
// 将棧頂存儲的傳回位址設定到%rax中,也就是将存儲位址0x00007fffe1017547
// 的棧的slot位址設定到%rax中
0x00007fffe101754c: lea 0x8(%rsp),%rax


// 調用InterpreterMacroAssembler::call_VM_base()函數
// 存儲bcp到棧中特定位置
0x00007fffe1017551: mov %r13,-0x38(%rbp)

// 調用MacroAssembler::call_VM_base()函數
// 将r15中的值移動到rdi寄存器中,也就是為函數調用準備第一個參數
0x00007fffe1017555: mov   %r15,%rdi
// 隻有解釋器才必須要設定fp
// 将last_java_fp儲存到JavaThread類的last_java_fp屬性中
0x00007fffe1017558: mov   %rbp,0x200(%r15)  
// 将last_java_sp儲存到JavaThread類的last_java_sp屬性中 
0x00007fffe101755f: mov   %rax,0x1f0(%r15)   

// ... 省略調用MacroAssembler::call_VM_leaf_base()函數

// 重置JavaThread::last_java_sp與JavaThread::last_java_fp屬性的值
0x00007fffe1017589: movabs $0x0,%r10
0x00007fffe1017593: mov %r10,0x1f0(%r15)
0x00007fffe101759a: movabs $0x0,%r10
0x00007fffe10175a4: mov %r10,0x200(%r15)

// check for pending exceptions (java_thread is set upon return)
0x00007fffe10175ab: cmpq  $0x0,0x8(%r15)
// 如果沒有異常則直接跳轉到ok
0x00007fffe10175b3: je    0x00007fffe10175be
// 如果有異常則跳轉到StubRoutines::forward_exception_entry()擷取的例程入口
0x00007fffe10175b9: jmpq  0x00007fffe1000420

// -- ok --
// 将JavaThread::vm_result屬性中的值存儲到%rax寄存器中并清空vm_result屬性的值
0x00007fffe10175be: mov     0x250(%r15),%rax
0x00007fffe10175c5: movabs  $0x0,%r10
0x00007fffe10175cf: mov     %r10,0x250(%r15)

// 結束調用MacroAssembler::call_VM_base()函數


// 恢複bcp與locals
0x00007fffe10175d6: mov   -0x38(%rbp),%r13
0x00007fffe10175da: mov   -0x30(%rbp),%r14


// 結束調用MacroAssembler::call_VM_helper()函數

0x00007fffe10175de: retq  
// 結束調用MacroAssembler::call_VM()函數
      

下面詳細解釋如下彙編的意思。  

call指令相當于如下兩條指令:

push %eip
jmp  addr      

而ret指令相當于:

pop %eip      

是以如上彙編代碼:

0x00007fffe1017542: callq  0x00007fffe101754c 
0x00007fffe1017547: jmpq   0x00007fffe10175df // 跳轉
...
0x00007fffe10175de: retq 
      

調用callq指令将jmpq的位址壓入了表達式棧,也就是壓入了傳回位址x00007fffe1017547,這樣當後續調用retq時,會跳轉到jmpq指令執行,而jmpq又跳轉到了0x00007fffe10175df位址處的指令執行。

通過調用MacroAssembler::call_VM()函數來調用HotSpot VM中用的C++編寫的函數,call_VM()函數還會調用如下函數:

MacroAssembler::call_VM_helper
   InterpreterMacroAssembler::call_VM_base()
       MacroAssembler::call_VM_base()
            MacroAssembler::call_VM_leaf_base()
      

在如上幾個函數中,最重要的就是在MacroAssembler::call_VM_base()函數中儲存rsp、rbp的值到JavaThread::last_java_sp與JavaThread::last_java_fp屬性中,然後通過MacroAssembler::call_VM_leaf_base()函數生成的彙編代碼來調用C++編寫的InterpreterRuntime::ldc()函數,如果調用InterpreterRuntime::ldc()函數有可能破壞rsp和rbp的值(其它的%r13、%r14等的寄存器中的值也有可能破壞,是以在必要時儲存到棧中,在調用完成後再恢複,這樣這些寄存器其實就算的上是調用者儲存的寄存器了),是以為了保證rsp、rbp,将這兩個值存儲到線程中,線上程中儲存的這2個值對于棧展開非常非常重要,後面我們會詳細介紹。

由于如上彙編代碼會解釋執行,在解釋執行過程中會調用C++函數,是以C/C++棧和Java棧都混在一起,這為我們查找帶來了一定的複雜度。

調用的MacroAssembler::call_VM_leaf_base()函數生成的彙編代碼如下:

第3部分彙編代碼:

// 調用MacroAssembler::call_VM_leaf_base()函數
0x00007fffe1017566: test  $0xf,%esp          // 檢查對齊
// %esp對齊的操作,跳轉到 L
0x00007fffe101756c: je    0x00007fffe1017584 
// %esp沒有對齊時的操作
0x00007fffe1017572: sub   $0x8,%rsp
0x00007fffe1017576: callq 0x00007ffff66a22a2  // 調用函數,也就是調用InterpreterRuntime::ldc()函數
0x00007fffe101757b: add   $0x8,%rsp
0x00007fffe101757f: jmpq  0x00007fffe1017589  // 跳轉到E2
// -- L --
// %esp對齊的操作
0x00007fffe1017584: callq 0x00007ffff66a22a2  // 調用函數,也就是調用InterpreterRuntime::ldc()函數

// -- E2 --

// 結束調用
MacroAssembler::call_VM_leaf_base()函數
      

在如上這段彙編中會真正調用C++函數InterpreterRuntime::ldc(),由于這是一個C++函數,是以在調用時,如果要傳遞參數,則要遵守C++調用約定,也就是前6個參數都放到固定的寄存器中。這個函數需要2個參數,分别為thread和wide,已經分别放到了%rdi和%rax寄存器中了。InterpreterRuntime::ldc()函數的實作如下:

// ldc負責将數值常量或String常量值從常量池中推送到棧頂
IRT_ENTRY(void, InterpreterRuntime::ldc(JavaThread* thread, bool wide))
  ConstantPool* pool = method(thread)->constants();
  int index = wide ? get_index_u2(thread, Bytecodes::_ldc_w) : get_index_u1(thread, Bytecodes::_ldc);
  constantTag tag = pool->tag_at(index);

  Klass* klass = pool->klass_at(index, CHECK);
  oop java_class = klass->java_mirror(); // java.lang.Class通過oop來表示
  thread->set_vm_result(java_class);
IRT_END
      

函數将查找到的、目前正在解釋執行的方法所屬的類存儲到JavaThread類的vm_result屬性中。我們可以回看第2部分彙編代碼,會将vm_result屬性的值設定到%rax中。

接下來繼續看TemplateTable::ldc(bool wide)函數生成的彙編代碼,此時已經通過調用call_VM()函數生成了調用InterpreterRuntime::ldc()這個C++的彙編,調用完成後值已經放到了%rax中。 

// -- E1 --  
0x00007fffe10287ba: push   %rax  // 将調用的結果存儲到表達式中
0x00007fffe10287bb: jmpq   0x00007fffe102885e // 跳轉到Done

// -- notClass --
// $0x4表示JVM_CONSTANT_Float
0x00007fffe10287c0: cmp    $0x4,%edx
0x00007fffe10287c3: jne    0x00007fffe10287d9 // 跳到notFloat
// 當ldc位元組碼指令加載的數為float時執行如下彙編代碼
0x00007fffe10287c5: vmovss 0x58(%rcx,%rbx,8),%xmm0
0x00007fffe10287cb: sub    $0x8,%rsp
0x00007fffe10287cf: vmovss %xmm0,(%rsp)
0x00007fffe10287d4: jmpq   0x00007fffe102885e // 跳轉到Done
 
// -- notFloat --
// 當ldc位元組碼指令加載的為非float,也就是int類型資料時通過push加入表達式棧
0x00007fffe1028859: mov    0x58(%rcx,%rbx,8),%eax
0x00007fffe102885d: push   %rax

// -- Done --
      

由于ldc指令除了加載String外,還可能加載int和float,如果是int,直接調用push壓入表達式棧中,如果是float,則在表達式棧上開辟空間,然後移到到這個開辟的slot中存儲。注意,float會使用%xmm0寄存器。 

 2、fast_aldc虛拟機内部位元組碼指令

下面介紹_fast_aldc指令,這個指令是虛拟機内部使用的指令而非虛拟機規範定義的指令。_fast_aldc指令的模闆定義如下:

def(Bytecodes::_fast_aldc , ubcp|____|clvm|____, vtos, atos, fast_aldc ,  false );      

生成函數為TemplateTable::fast_aldc(bool wide),這個函數生成的彙編代碼如下:

// 調用InterpreterMacroAssembler::get_cache_index_at_bcp()函數生成
// 擷取位元組碼指令的操作數,這個操作數已經指向了常量池緩存項的索引,在位元組碼重寫
// 階段已經進行了位元組碼重寫
0x00007fffe10243d0: movzbl 0x1(%r13),%edx

// 調用InterpreterMacroAssembler::load_resolved_reference_at_index()函數生成

// shl表示邏輯左移,相當于乘4,因為ConstantPoolCacheEntry的大小為4個字
0x00007fffe10243d5: shl    $0x2,%edx

// 擷取Method*
0x00007fffe10243d8: mov    -0x18(%rbp),%rax
// 擷取ConstMethod*
0x00007fffe10243dc: mov    0x10(%rax),%rax
// 擷取ConstantPool*
0x00007fffe10243e0: mov    0x8(%rax),%rax
// 擷取ConstantPool::_resolved_references屬性的值,這個值
// 是一個指向對象數組的指針
0x00007fffe10243e4: mov    0x30(%rax),%rax

// JNIHandles::resolve(obj)
0x00007fffe10243e8: mov    (%rax),%rax

// 從_resolved_references數組指定的下标索引處擷取oop,先進行索引偏移
0x00007fffe10243eb: add    %rdx,%rax

// 要在%rax上加0x10,是因為數組對象的頭大小為2個字,加上後
// %rax就指向了oop
0x00007fffe10243ee: mov    0x10(%rax),%eax
      

擷取_resolved_references屬性的值,涉及到的2個屬性在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;      

關于_resolved_references指向的其實是Object數組。在ConstantPool::initialize_resolved_references()函數中初始化這個屬性。調用鍊如下:

ConstantPool::initialize_resolved_references()  constantPool.cpp   	
Rewriter::make_constant_pool_cache()  rewriter.cpp	
Rewriter::Rewriter()                  rewriter.cpp
Rewriter::rewrite()                   rewriter.cpp
InstanceKlass::rewrite_class()        instanceKlass.cpp	
InstanceKlass::link_class_impl()      instanceKlass.cpp      

後續如果需要連接配接ldc等指令時,可能會調用如下函數:(我們隻讨論ldc加載String類型資料的問題,是以我們隻看往_resolved_references屬性中放入表示String的oop的邏輯,MethodType與MethodHandle将不再介紹,有興趣的可自行研究)

oop ConstantPool::string_at_impl(
 constantPoolHandle this_oop, 
 int    which, 
 int    obj_index, 
 TRAPS
) {
  oop str = this_oop->resolved_references()->obj_at(obj_index);
  if (str != NULL)
	  return str;

  Symbol* sym = this_oop->unresolved_string_at(which);
  str = StringTable::intern(sym, CHECK_(NULL));

  this_oop->string_at_put(which, obj_index, str);

  return str;
}

void string_at_put(int which, int obj_index, oop str) {
  // 擷取類型為jobject的_resolved_references屬性的值
  objArrayOop tmp = resolved_references();
  tmp->obj_at_put(obj_index, str);
}
      

在如上函數中向_resolved_references數組中設定緩存的值。

大概的思路就是:如果ldc加載的是字元串,那麼盡量通過_resolved_references數組中一次性找到表示字元串的oop,否則要通過原常量池下标索引找到Symbol執行個體(Symbol執行個體是HotSpot VM内部使用的、用來表示字元串),根據Symbol執行個體生成對應的oop,然後通過常量池緩存下标索引設定到_resolved_references中。當下次查找時,通過這個常量池緩存下标緩存找到表示字元串的oop。

擷取到_resolved_references屬性的值後接着看生成的彙編代碼,如下:

// ...
// %eax中存儲着表示字元串的oop
0x00007fffe1024479: test   %eax,%eax
// 如果已經擷取到了oop,則跳轉到resolved
0x00007fffe102447b: jne    0x00007fffe1024481

// 沒有擷取到oop,需要進行連接配接操作,0xe5是_fast_aldc的Opcode
0x00007fffe1024481: mov    $0xe5,%edx  
      

調用call_VM()函數生成的彙編代碼如下:

// 調用InterpreterRuntime::resolve_ldc()函數
0x00007fffe1024486: callq  0x00007fffe1024490
0x00007fffe102448b: jmpq   0x00007fffe1024526

// 将%rdx中的ConstantPoolCacheEntry項存儲到第1個參數中

// 調用MacroAssembler::call_VM_helper()函數生成
0x00007fffe1024490: mov    %rdx,%rsi
// 将傳回位址加載到%rax中
0x00007fffe1024493: lea    0x8(%rsp),%rax

// 調用call_VM_base()函數生成
// 儲存bcp
0x00007fffe1024498: mov    %r13,-0x38(%rbp)

// 調用MacroAssembler::call_VM_base()函數生成

// 将r15中的值移動到c_rarg0(rdi)寄存器中,也就是為函數調用準備第一個參數
0x00007fffe102449c: mov    %r15,%rdi
// Only interpreter should have to set fp 隻有解釋器才必須要設定fp
0x00007fffe102449f: mov    %rbp,0x200(%r15)
0x00007fffe10244a6: mov    %rax,0x1f0(%r15)

// 調用MacroAssembler::call_VM_leaf_base()生成
0x00007fffe10244ad: test   $0xf,%esp
0x00007fffe10244b3: je     0x00007fffe10244cb
0x00007fffe10244b9: sub    $0x8,%rsp
0x00007fffe10244bd: callq  0x00007ffff66b27ac
0x00007fffe10244c2: add    $0x8,%rsp
0x00007fffe10244c6: jmpq   0x00007fffe10244d0
0x00007fffe10244cb: callq  0x00007ffff66b27ac
0x00007fffe10244d0: movabs $0x0,%r10
// 結束調用MacroAssembler::call_VM_leaf_base()

0x00007fffe10244da: mov    %r10,0x1f0(%r15)
0x00007fffe10244e1: movabs $0x0,%r10

// 檢查是否有異常發生
0x00007fffe10244eb: mov    %r10,0x200(%r15)
0x00007fffe10244f2: cmpq   $0x0,0x8(%r15)
// 如果沒有異常發生,則跳轉到ok
0x00007fffe10244fa: je     0x00007fffe1024505
// 有異常發生,則跳轉到StubRoutines::forward_exception_entry()
0x00007fffe1024500: jmpq   0x00007fffe1000420

// ---- ok ----

// 将JavaThread::vm_result屬性中的值存儲到oop_result寄存器中并清空vm_result屬性的值
0x00007fffe1024505: mov    0x250(%r15),%rax
0x00007fffe102450c: movabs $0x0,%r10
0x00007fffe1024516: mov    %r10,0x250(%r15)

// 結果調用MacroAssembler::call_VM_base()函數

// 恢複bcp和locals
0x00007fffe102451d: mov    -0x38(%rbp),%r13
0x00007fffe1024521: mov    -0x30(%rbp),%r14

// 結束調用InterpreterMacroAssembler::call_VM_base()函數
// 結束調用MacroAssembler::call_VM_helper()函數

0x00007fffe1024525: retq   

// 結束調用MacroAssembler::call_VM()函數,回到
// TemplateTable::fast_aldc()函數繼續看生成的代碼,隻
// 定義了resolved點

// ---- resolved ----  
      

調用的InterpreterRuntime::resolve_ldc()函數的實作如下:

IRT_ENTRY(void, InterpreterRuntime::resolve_ldc(
 JavaThread* thread, 
 Bytecodes::Code bytecode)
) {
  ResourceMark rm(thread);
  methodHandle m (thread, method(thread));
  Bytecode_loadconstant  ldc(m, bci(thread));
  oop result = ldc.resolve_constant(CHECK);

  thread->set_vm_result(result);
}
IRT_END
      

這個函數會調用一系列的函數,相關調用鍊如下:

ConstantPool::string_at_put()   constantPool.hpp
ConstantPool::string_at_impl()  constantPool.cpp
ConstantPool::resolve_constant_at_impl()     constantPool.cpp	
ConstantPool::resolve_cached_constant_at()   constantPool.hpp	
Bytecode_loadconstant::resolve_constant()    bytecode.cpp	
InterpreterRuntime::resolve_ldc()            interpreterRuntime.cpp	        

其中ConstantPool::string_at_impl()函數在前面已經詳細介紹過。 

調用的resolve_constant()函數的實作如下:

oop Bytecode_loadconstant::resolve_constant(TRAPS) const {
  int index = raw_index();
  ConstantPool* constants = _method->constants();
  if (has_cache_index()) {
    return constants->resolve_cached_constant_at(index, THREAD);
  } else {
    return constants->resolve_constant_at(index, THREAD);
  }
}      

調用的resolve_cached_constant_at()或resolve_constant_at()函數的實作如下:

oop resolve_cached_constant_at(int cache_index, TRAPS) {
    constantPoolHandle h_this(THREAD, this);
    return resolve_constant_at_impl(h_this, _no_index_sentinel, cache_index, THREAD);
}

oop resolve_possibly_cached_constant_at(int pool_index, TRAPS) {
    constantPoolHandle h_this(THREAD, this);
    return resolve_constant_at_impl(h_this, pool_index, _possible_index_sentinel, THREAD);
}      

調用的resolve_constant_at_impl()函數的實作如下:

oop ConstantPool::resolve_constant_at_impl(
 constantPoolHandle this_oop,
 int index,
 int cache_index,
 TRAPS
) {
  oop result_oop = NULL;
  Handle throw_exception;

  if (cache_index == _possible_index_sentinel) {
    cache_index = this_oop->cp_to_object_index(index);
  }

  if (cache_index >= 0) {
    result_oop = this_oop->resolved_references()->obj_at(cache_index);
    if (result_oop != NULL) {
      return result_oop;
    }
    index = this_oop->object_to_cp_index(cache_index);
  }

  jvalue prim_value;  // temp used only in a few cases below

  int tag_value = this_oop->tag_at(index).value();

  switch (tag_value) {
  // ...
  case JVM_CONSTANT_String:
    assert(cache_index != _no_index_sentinel, "should have been set");
    if (this_oop->is_pseudo_string_at(index)) {
      result_oop = this_oop->pseudo_string_at(index, cache_index);
      break;
    }
    result_oop = string_at_impl(this_oop, index, cache_index, CHECK_NULL);
    break;
  // ...
  }

  if (cache_index >= 0) {
    Handle result_handle(THREAD, result_oop);
    MonitorLockerEx ml(this_oop->lock());  
    oop result = this_oop->resolved_references()->obj_at(cache_index);
    if (result == NULL) {
      this_oop->resolved_references()->obj_at_put(cache_index, result_handle());
      return result_handle();
    } else {
      return result;
    }
  } else {
    return result_oop;
  }
}      

通過常量池的tags數組判斷,如果常量池下标index處存儲的是JVM_CONSTANT_String常量池項,則調用string_at_impl()函數,這個函數在之前已經介紹過,會根據表示字元串的Symbol執行個體建立出表示字元串的oop。在ConstantPool::resolve_constant_at_impl()函數中得到oop後就存儲到ConstantPool::_resolved_references屬性中,最後傳回這個oop,這正是ldc需要的oop。 

通過重寫fast_aldc位元組碼指令,達到了通過少量指令就直接擷取到oop的目的,而且oop是緩存的,是以字元串常量在HotSpot VM中的表示唯一,也就是隻有一個oop表示。  

C++函數約定傳回的值會存儲到%rax中,根據_fast_aldc位元組碼指令的模闆定義可知,tos_out為atos,是以後續并不需要進一步操作。 

HotSpot VM會在類的連接配接過程中重寫某些位元組碼,如ldc位元組碼重寫為fast_aldc,還有常量池的tags類型數組、常量池緩存等内容在《深入剖析Java虛拟機:源碼剖析與執行個體詳解》中詳細介紹過,這裡不再介紹。

推薦閱讀:

第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

第12篇-認識CodeletMark

第13篇-通過InterpreterCodelet存儲機器指令片段

第14篇-生成重要的例程

第15章-解釋器及解釋器生成器

第16章-虛拟機中的彙編器

第17章-x86-64寄存器

第18章-x86指令集之常用指令

第19篇-加載與存儲指令(1)

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

關注公衆号,有HotSpot VM源碼剖析系列文章!

  

  

  

第20篇-加載與存儲指令之ldc與_fast_aldc指令(2)
上一篇: snmp4j擷取表
下一篇: snmp4j詳解