getfield指令表示獲取指定類的例項域,並將其值壓入棧頂。其格式如下:

getstatic indexbyte1 indexbyte2

無符號數indexbyte1和indexbyte2構建為(indexbyte1<<8)|indexbyte2,這個值指明瞭一個當前類的執行時常量池索引值,指向的執行時常量池項為一個欄位的符號引用。

getfield位元組碼指令的生成函式為TemplateTable::getfield(),這些生成函式如下:

void TemplateTable::getfield(int byte_no) {
getfield_or_static(byte_no, false); // getfield的byte_no值為1
}

最終會呼叫getfield_or_static()函式生成機器指令片段。此函式生成的機器指令片段對應的彙編程式碼如下:

0x00007fffe10202d0: movzwl 0x1(%r13),%edx
0x00007fffe10202d5: mov -0x28(%rbp),%rcx
0x00007fffe10202d9: shl $0x2,%edx
0x00007fffe10202dc: mov 0x10(%rcx,%rdx,8),%ebx
0x00007fffe10202e0: shr $0x10,%ebx
0x00007fffe10202e3: and $0xff,%ebx
// 0xb4是getfield指令的Opcode,如果相等,則說明已經連線,直接跳轉到resolved
0x00007fffe10202e9: cmp $0xb4,%ebx
0x00007fffe10202ef: je 0x00007fffe102038e 0x00007fffe10202f5: mov $0xb4,%ebx
// 省略通過呼叫MacroAssembler::call_VM()函式來執行
// InterpreterRuntime::resolve_get_put()函式的彙編程式碼
// ...

呼叫MacroAssembler::call_VM()函式生成如下程式碼,通過這些程式碼來執行InterpreterRuntime::resolve_get_put()函式。MacroAssembler::call_VM()函式的彙編在之前已經詳細介紹過,這裡不再介紹,直接給出彙編程式碼,如下:

0x00007fffe10202fa: callq  0x00007fffe1020304
0x00007fffe10202ff: jmpq 0x00007fffe1020382 0x00007fffe1020304: mov %rbx,%rsi
0x00007fffe1020307: lea 0x8(%rsp),%rax
0x00007fffe102030c: mov %r13,-0x38(%rbp)
0x00007fffe1020310: mov %r15,%rdi
0x00007fffe1020313: mov %rbp,0x200(%r15)
0x00007fffe102031a: mov %rax,0x1f0(%r15)
0x00007fffe1020321: test $0xf,%esp
0x00007fffe1020327: je 0x00007fffe102033f
0x00007fffe102032d: sub $0x8,%rsp
0x00007fffe1020331: callq 0x00007ffff66b567c
0x00007fffe1020336: add $0x8,%rsp
0x00007fffe102033a: jmpq 0x00007fffe1020344
0x00007fffe102033f: callq 0x00007ffff66b567c
0x00007fffe1020344: movabs $0x0,%r10
0x00007fffe102034e: mov %r10,0x1f0(%r15)
0x00007fffe1020355: movabs $0x0,%r10
0x00007fffe102035f: mov %r10,0x200(%r15)
0x00007fffe1020366: cmpq $0x0,0x8(%r15)
0x00007fffe102036e: je 0x00007fffe1020379
0x00007fffe1020374: jmpq 0x00007fffe1000420
0x00007fffe1020379: mov -0x38(%rbp),%r13
0x00007fffe102037d: mov -0x30(%rbp),%r14
0x00007fffe1020381: retq

如上程式碼完成的事情很簡單,就是呼叫C++函式編寫的InterpreterRuntime::resolve_get_put()函式,此函式會填充常量池快取中ConstantPoolCacheEntry資訊,關於ConstantPoolCache以及ConstantPoolCacheEntry,還有ConstantPoolCacheEntry中各個欄位的含義在《深入剖析Java虛擬機器:原始碼剖析與例項詳解(基礎卷)》中已經詳細介紹過,這裡不再介紹。

0x00007fffe1020382: movzwl 0x1(%r13),%edx
0x00007fffe1020387: mov -0x28(%rbp),%rcx
0x00007fffe102038b: shl $0x2,%edx ---- resolved ---- // 獲取[_indices,_f1,_f2,_flags]中的_f2,由於ConstantPoolCache佔用16位元組,而_indices
// 和_f2各佔用8位元組,所以_f2的偏移為32位元組,也就是0x32
// _f2中儲存的是欄位在oop例項中的位元組偏移,通過此偏移就可獲取此欄位儲存在
// oop中的值
0x00007fffe102038e: mov 0x20(%rcx,%rdx,8),%rbx // 獲取[_indices,_f1,_f2,_flags]中的_flags
0x00007fffe1020393: mov 0x28(%rcx,%rdx,8),%eax // 將棧中的objectref物件彈出到%rcx中
0x00007fffe1020397: pop %rcx // provoke(激起; 引起; 引發) OS NULL exception if reg = NULL by
// accessing M[reg] w/o changing any (non-CC) registers
// NOTE: cmpl is plenty(足夠) here to provoke a segv
0x00007fffe1020398: cmp (%rcx),%rax // 將_flags向右移動28位,剩下TosState
0x00007fffe102039b: shr $0x1c,%eax
0x00007fffe102039e: and $0xf,%eax
// 如果不相等,說明TosState的值不為0,則跳轉到notByte
0x00007fffe10203a1: jne 0x00007fffe10203ba // btos // btos的編號為0,程式碼執行到這裡時,可能棧頂快取要求是btos
// %rcx中儲存的是objectref,%rbx中儲存的是_f2,獲取欄位對應的值儲存到%rax中
0x00007fffe10203a7: movsbl (%rcx,%rbx,1),%eax
0x00007fffe10203ab: push %rax // 對位元組碼指令進行重寫,將Bytecodes::_fast_bgetfield的Opcode儲存到%ecx中
0x00007fffe10203ac: mov $0xcc,%ecx
// 將Bytecodes::_fast_bgetfield的Opcode更新到位元組碼指令的操作碼
0x00007fffe10203b1: mov %cl,0x0(%r13)
// 跳轉到---- Done ----
0x00007fffe10203b5: jmpq 0x00007fffe102050f
---- notByte ----
0x00007fffe10203ba: cmp $0x7,%eax
0x00007fffe10203bd: jne 0x00007fffe102045d // 跳轉到notObj // atos // 呼叫MacroAssembler::load_heap_oop()函式生成如下程式碼
0x00007fffe10203c3: mov (%rcx,%rbx,1),%eax
// ... 省略部分程式碼
// 結束MacroAssembler::load_heap_oop()函式的呼叫
0x00007fffe102044e: push %rax
// 重寫位元組碼指令為Bytecodes::_fast_agetfield
0x00007fffe102044f: mov $0xcb,%ecx
0x00007fffe1020454: mov %cl,0x0(%r13)
0x00007fffe1020458: jmpq 0x00007fffe102050f
// -- notObj --
0x00007fffe102045d: cmp $0x3,%eax
0x00007fffe1020460: jne 0x00007fffe1020478 // 跳轉到notInt // itos 0x00007fffe1020466: mov (%rcx,%rbx,1),%eax
0x00007fffe1020469: push %rax
// 重寫位元組碼指令o Bytecodes::_fast_igetfield
0x00007fffe102046a: mov $0xd0,%ecx
0x00007fffe102046f: mov %cl,0x0(%r13)
0x00007fffe1020473: jmpq 0x00007fffe102050f
// --- notInt ----
0x00007fffe1020478: cmp $0x1,%eax
0x00007fffe102047b: jne 0x00007fffe1020494 // 跳轉到notChar // ctos 0x00007fffe1020481: movzwl (%rcx,%rbx,1),%eax
0x00007fffe1020485: push %rax
// 重寫位元組碼指令為Bytecodes::_fast_cgetfield
0x00007fffe1020486: mov $0xcd,%ecx
0x00007fffe102048b: mov %cl,0x0(%r13)
0x00007fffe102048f: jmpq 0x00007fffe102050f
// ---- notChar ----
0x00007fffe1020494: cmp $0x2,%eax
0x00007fffe1020497: jne 0x00007fffe10204b0 // 跳轉到notShort // stos 0x00007fffe102049d: movswl (%rcx,%rbx,1),%eax
0x00007fffe10204a1: push %rax
// 重寫位元組碼指令為Bytecodes::_fast_sgetfield
0x00007fffe10204a2: mov $0xd2,%ecx
0x00007fffe10204a7: mov %cl,0x0(%r13)
0x00007fffe10204ab: jmpq 0x00007fffe102050f
// ---- notShort ----
0x00007fffe10204b0: cmp $0x4,%eax
0x00007fffe10204b3: jne 0x00007fffe10204d3 // 跳轉到notLong // ltos 0x00007fffe10204b9: mov (%rcx,%rbx,1),%rax
0x00007fffe10204bd: sub $0x10,%rsp
0x00007fffe10204c1: mov %rax,(%rsp)
// 重寫位元組碼指令為Bytecodes::_fast_lgetfield,
0x00007fffe10204c5: mov $0xd1,%ecx
0x00007fffe10204ca: mov %cl,0x0(%r13)
0x00007fffe10204ce: jmpq 0x00007fffe102050f
// ---- notLong ----
0x00007fffe10204d3: cmp $0x5,%eax
0x00007fffe10204d6: jne 0x00007fffe10204f8 // 跳轉到notFloat // ftos
0x00007fffe10204dc: vmovss (%rcx,%rbx,1),%xmm0
0x00007fffe10204e1: sub $0x8,%rsp
0x00007fffe10204e5: vmovss %xmm0,(%rsp)
// 重寫位元組碼指令為Bytecodes::_fast_fgetfield
0x00007fffe10204ea: mov $0xcf,%ecx
0x00007fffe10204ef: mov %cl,0x0(%r13)
0x00007fffe10204f3: jmpq 0x00007fffe102050f
// ---- notFloat ----
0x00007fffe10204f8: vmovsd (%rcx,%rbx,1),%xmm0
0x00007fffe10204fd: sub $0x10,%rsp
0x00007fffe1020501: vmovsd %xmm0,(%rsp)
0x00007fffe1020506: mov $0xce,%ecx
0x00007fffe102050b: mov %cl,0x0(%r13) // -- Done --  
  

我們需要介紹一下虛擬機器內部的一些自定義指令,這些自定義指令的模板如下:

// JVM bytecodes
def(Bytecodes::_fast_agetfield , ubcp|____|____|____, atos, atos, fast_accessfield , atos );
def(Bytecodes::_fast_bgetfield , ubcp|____|____|____, atos, itos, fast_accessfield , itos );
def(Bytecodes::_fast_cgetfield , ubcp|____|____|____, atos, itos, fast_accessfield , itos );
def(Bytecodes::_fast_dgetfield , ubcp|____|____|____, atos, dtos, fast_accessfield , dtos );
def(Bytecodes::_fast_fgetfield , ubcp|____|____|____, atos, ftos, fast_accessfield , ftos );
def(Bytecodes::_fast_igetfield , ubcp|____|____|____, atos, itos, fast_accessfield , itos );
def(Bytecodes::_fast_lgetfield , ubcp|____|____|____, atos, ltos, fast_accessfield , ltos );
def(Bytecodes::_fast_sgetfield , ubcp|____|____|____, atos, itos, fast_accessfield , itos );

以_fast_agetfield內部定義的位元組碼指令為例為來,生成函式為TemplateTable::fast_accessfield()函式,彙編程式碼如下:

0x00007fffe101e4e1: movzwl 0x1(%r13),%ebx
0x00007fffe101e4e6: mov -0x28(%rbp),%rcx
0x00007fffe101e4ea: shl $0x2,%ebx
// 計算%rcx+%rdx*8+0x20,獲取ConstantPoolCacheEntry[_indices,_f1,_f2,_flags]中的_f2
// 因為ConstantPoolCache的大小為0x16位元組,%rcx+0x20定位到第一個ConstantPoolCacheEntry的開始位置
// %rdx*8算出來的是相對於第一個ConstantPoolCacheEntry的位元組偏移
0x00007fffe101e4ed: mov 0x20(%rcx,%rbx,8),%rbx // 檢查空異常
0x00007fffe101e4f2: cmp (%rax),%rax
// %rax中儲存的是objectref,也就是要從這個例項中獲取欄位的值,通過偏移%rbx後就
// 能獲取到偏移的值,然後載入到%eax
0x00007fffe101e4f5: mov (%rax,%rbx,1),%eax
  

其它的位元組碼指令類似,這裡不再過多介紹。從這裡可以看出,我們不需要再執行getfield對應的那些彙編指令,只執行_fast開頭的指令即可,這些指令比起getfield指令來說簡化了很多,大大提高了解釋執行的速度。  

推薦閱讀:

第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)

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

第21篇-虛擬機器位元組碼之運算指令

第22篇-虛擬機器位元組碼指令之型別轉換

第23篇-虛擬機器物件操作指令之getstatic

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

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