字節(jié)碼的基本結(jié)構(gòu)
一個(gè)普通類(lèi)的java代碼
public class Parent {
public void sayHi(){
}
}
編譯原理是這么描述編譯的:將某一種語(yǔ)言(源語(yǔ)言)編寫(xiě)的程序,翻譯成為一個(gè)等價(jià)的、用另一種語(yǔ)言(目標(biāo)語(yǔ)言)編寫(xiě)的程序。那么對(duì)于java而言,就是將java代碼翻譯成字節(jié)碼,那么上面這個(gè)簡(jiǎn)單的類(lèi),字節(jié)碼是怎么描述的呢,使用 javap -v Parent.class可以看到字節(jié)碼的相應(yīng)結(jié)構(gòu)
public class Parent
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool: 常量池
#1 = Methodref #3.#11 // java/lang/Object."<init>":()V
#2 = Class #12 // Parent
#3 = Class #13 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 sayHi
#9 = Utf8 SourceFile
#10 = Utf8 Parent.java
#11 = NameAndType #4:#5 // "<init>":()V
#12 = Utf8 Parent
#13 = Utf8 java/lang/Object
{ 方法表
public Parent();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public void sayHi();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 3: 0
}
其中,constant pool代表常量池,它可以理解為class文件中的資源倉(cāng)庫(kù),常量池中主要存放兩大類(lèi)常量,字面量和符號(hào)引用,字面量比較接近于java層面的常量概念,如文本字符串,聲明為final的常量值等,符號(hào)引用則是屬于編譯原理方面的內(nèi)容,本例中類(lèi)型為Utf8的就是字面量,其他都是符號(hào)引用
Constant pool:
#2 = Class #12 // Parent
#3 = Class #13 // java/lang/Object
#11 = NameAndType #4:#5 // "<init>":()
#12 = Utf8 Parent
#13 = Utf8 java/lang/Object
首先來(lái)看這部分常量,常量#2和#3是是符號(hào)引用的第一種類(lèi)型: 類(lèi)和接口的全限定名,常量#2是本類(lèi)的全限定名(Parent),常量#3是父類(lèi)的全限定名(java/lang/Object),這樣字節(jié)碼就將繼承關(guān)系簡(jiǎn)單的描述了出來(lái)
再看第一個(gè)常量#1,這是另一個(gè)類(lèi)型的符號(hào)引用:方法的名稱(chēng)和描述符
#1 = Methodref #3.#11 // java/lang/Object."<init>":()V
#3 = Class #13 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#11 = NameAndType #4:#5 // "<init>":()
方法的符號(hào)引用比較有意思,可以看到,#1是由#3.#11組合而來(lái),簡(jiǎn)單拼一下就是 #3.#11=Class(#13). NameAndType(#4:#5) = java/lang/Object."<init>":()V,翻譯一下就是類(lèi)java/lang/Object(父類(lèi))的,方法名為<init>的,返回值為void的,沒(méi)有入?yún)⒌姆?hào)引用。
對(duì)于jvm而言,如果要調(diào)用一個(gè)方法,只能根據(jù)上述的符號(hào)引用(java/lang/Object."<init>":()V)來(lái)找到這個(gè)方法,所以必須保證它的精確性和唯一性,本例中,通過(guò)類(lèi)的全限定名、方法名、返回值、入?yún)⒘斜?精確描述了一個(gè)方法
那么方法的符號(hào)引用會(huì)在那部分使用到呢,在如下的方法表中,有個(gè)Code區(qū),里面存放是java代碼編譯而來(lái)的字節(jié)碼指令
public Parent();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
從方法名可以看出,本方法是編譯器自動(dòng)生成的無(wú)參構(gòu)造方法Parent(),里面 invokespecial #1 這一條指令即調(diào)用常量#1對(duì)應(yīng)的方法引用,即父類(lèi)方法java/lang/Object."<init>":()V, invokespecial會(huì)在后文進(jìn)一步介紹,這里可以簡(jiǎn)單理解為調(diào)用方法的指令
<init>()方法與構(gòu)造方法的關(guān)系
再看這個(gè)指令的細(xì)節(jié),<init>方法是編譯器自動(dòng)生成的方法,名為實(shí)例構(gòu)造器,奇怪的是,構(gòu)造函數(shù)Parent()中只調(diào)用了父類(lèi)的<init>方法,沒(méi)有沒(méi)有調(diào)用自己的<init>方法,那這個(gè)<init>方法和構(gòu)造方法有什么關(guān)系呢,是不是包含關(guān)系?可以驗(yàn)證一下
public class Parent1 {
public Parent1(){
System.out.println("hello world");
}
public void sayHi(){
Parent1 parent = new Parent1();
}
}
給類(lèi)Parent1顯式定義一個(gè)無(wú)參構(gòu)造函數(shù)以及一個(gè)成員方法,javap -v一下:
public void sayHi();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=1
0: new #5 // class Parent1
3: dup
4: invokespecial #6 // Method "<init>":()V
7: astore_1
8: return
可以看到,sayHi()這個(gè)方法,在new Parent1()時(shí),只有對(duì)Parent1的<init>方法的調(diào)用,沒(méi)有對(duì)無(wú)參構(gòu)造方法Parent1()方法的調(diào)用,那么<init>方法必然包含了執(zhí)行構(gòu)造方法
public Parent1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #3 // String hello world
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: return
再看顯式聲明的構(gòu)造函數(shù)Parent1(),父類(lèi)的<init>方法在具體邏輯(hello world)之前,
那么可以得出結(jié)論:<init>方法會(huì)在實(shí)例化的時(shí)候調(diào)用,且<init>方法包含了構(gòu)造方法的邏輯,而構(gòu)造方法中,在其他邏輯之前顯式調(diào)用了父類(lèi)的<init>方法,這與我們的java常識(shí)也是相符的
符號(hào)引用如何解析為直接引用
通過(guò)以上的例子,應(yīng)該對(duì)類(lèi)結(jié)構(gòu)和字節(jié)碼有了基本的認(rèn)識(shí),可能會(huì)有一個(gè)疑惑,字節(jié)碼中的常量池里存放的都只是符號(hào)引用,那jvm是如何通過(guò)符號(hào)引用定位到真正的地址呢
這也是Java與c不同的地方,c語(yǔ)言將.c源文件編譯成.o文件后,.o文件中的引用直接就是使用的分配好的虛擬地址,而java的class文件中引用就僅僅是一個(gè)描述性的符號(hào),并沒(méi)有保存最終的內(nèi)存布局信息,那么引出兩個(gè)問(wèn)題,java編譯器如何保證能夠讓虛擬機(jī)準(zhǔn)確定位到符號(hào)引用指向的實(shí)際引用,以及虛擬機(jī)在什么時(shí)候?qū)⒎?hào)引用轉(zhuǎn)為實(shí)際引用
符號(hào)引用:是以一組符號(hào)來(lái)描述所引用的目標(biāo),符號(hào)可以是任何形式的字面量,只要使用時(shí)能無(wú)歧義地定位到目標(biāo)即可. 符號(hào)引用的目標(biāo)不一定要加載到內(nèi)存中.
直接引用:是直接指向目標(biāo)的指針、相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄. 如果有了直接引用,那引用的目標(biāo)必定存在于內(nèi)存中.
第一個(gè)問(wèn)題上文也提到了,符號(hào)引用包含足夠的信息,以供jvm實(shí)際使用時(shí)可以找到相應(yīng)的位置,比如:“java/io/PrintStream.println:(Ljava/lang/String;)V”,虛擬機(jī)就會(huì)將其轉(zhuǎn)為實(shí)際引用,這個(gè)過(guò)程叫做解析,但是解析的時(shí)機(jī)并不是固定的
虛擬機(jī)規(guī)范中并沒(méi)有明確規(guī)定解析階段發(fā)生的具體時(shí)間,只要求了在執(zhí)行 anewarray、checkcast、getfield、getstatic、instanceof、invokedynamic、invokeinterface、invokespecial、invokestatic、invokevirtual、ldc、ldc_w、multianewarray、new、putfield、putstatic 用于操作符號(hào)引用的字節(jié)碼指令前,先對(duì)它們所使用的符號(hào)進(jìn)行解析. 所以虛擬機(jī)實(shí)現(xiàn)可以根據(jù)需要來(lái)判斷到底是在類(lèi)被加載時(shí)就對(duì)常量池中的符號(hào)進(jìn)行解析,還是等到一個(gè)符號(hào)將要被使用前才解析它.

以上是類(lèi)加載的流程,解析是連接中的一個(gè)部分,也是本文的重點(diǎn),將通過(guò)擼hotspot的源碼來(lái)配合理解這個(gè)過(guò)程,源代碼在下圖的這個(gè)路徑下

首先看類(lèi)連接的時(shí)機(jī),以下是類(lèi)初始化函數(shù)的部分代碼,可以看出,第一行邏輯就是進(jìn)行類(lèi)解析,解析完之后才會(huì)繼續(xù)初始化,與常識(shí)相符(先連接再初始化)
void InstanceKlass::initialize_impl(instanceKlassHandle this_oop, TRAPS) {
// Make sure klass is linked (verified) before initialization
// A class could already be verified, since it has been reflected upon.
this_oop->link_class(CHECK);
...
}
順便看下InstanceKlass是個(gè)什么東西,跟類(lèi)有什么關(guān)系
// An InstanceKlass is the VM level representation of a Java class.
// It contains all information needed for at class at execution runtime.
// InstanceKlass layout:
// [C++ vtbl pointer ] Klass
// [subtype cache ] Klass
// [instance size ] Klass
// [java mirror ] Klass
// [super ] Klass
// [access_flags ] Klass
// [name ] Klass
// [first subklass ] Klass
// [next sibling ] Klass
// [array klasses ]
// [methods ]
// [local interfaces ]
// [transitive interfaces ]
// [fields ]
// [constants ]
// [class loader ]
// [source file name ]
// [inner classes ]
// [static field size ]
// [nonstatic field size ]
// [static oop fields size ]
// [nonstatic oop maps size ]
// [has finalize method ]
// [deoptimization mark bit ]
// [initialization state ]
// [initializing thread ]
// [Java vtable length ]
// [oop map cache (stack maps) ]
// [EMBEDDED Java vtable ] size in words = vtable_len
// [EMBEDDED nonstatic oop-map blocks] size in words = nonstatic_oop_map_size
// The embedded nonstatic oop-map blocks are short pairs (offset, length)
// indicating where oops are located in instances of this klass.
// [EMBEDDED implementor of the interface] only exist for interface
// [EMBEDDED host klass ] only exist for an anonymous class (JSR 292 en
InstanceKlass存著Java類(lèi)型的名字、繼承關(guān)系、實(shí)現(xiàn)接口關(guān)系,字段信息,方法信息,運(yùn)行時(shí)常量池的指針,還有內(nèi)嵌的虛方法表(vtable)、接口方法表(itable)和記錄對(duì)象里什么位置上有GC會(huì)關(guān)心的指針(oop map)等等。
是給VM內(nèi)部用的,并不直接暴露給Java層;InstanceKlass不是java.lang.Class的實(shí)例。
再看看類(lèi)連接的邏輯:
bool InstanceKlass::link_class_impl(
instanceKlassHandle this_oop, bool throw_verifyerror, TRAPS) {
// check for error state
if (this_oop->is_in_error_state()) {
ResourceMark rm(THREAD);
THROW_MSG_(vmSymbols::java_lang_NoClassDefFoundError(),
this_oop->external_name(), false);
}
// return if already verified
if (this_oop->is_linked()) {
return true;
}
// Timing
// timer handles recursion
assert(THREAD->is_Java_thread(), "non-JavaThread in link_class_impl");
JavaThread* jt = (JavaThread*)THREAD;
// link super class before linking this class
instanceKlassHandle super(THREAD, this_oop->super());
if (super.not_null()) {
if (super->is_interface()) { // check if super class is an interface
ResourceMark rm(THREAD);
Exceptions::fthrow(
THREAD_AND_LOCATION,
vmSymbols::java_lang_IncompatibleClassChangeError(),
"class %s has interface %s as super class",
this_oop->external_name(),
super->external_name()
);
return false;
}
link_class_impl(super, throw_verifyerror, CHECK_false);
}
// link all interfaces implemented by this class before linking this class
Array<Klass*>* interfaces = this_oop->local_interfaces();
int num_interfaces = interfaces->length();
for (int index = 0; index < num_interfaces; index++) {
HandleMark hm(THREAD);
instanceKlassHandle ih(THREAD, interfaces->at(index));
link_class_impl(ih, throw_verifyerror, CHECK_false);
}
// in case the class is linked in the process of linking its superclasses
if (this_oop->is_linked()) {
return true;
}
注釋比較清晰,比較容易捋出大體的流程,為了直觀一點(diǎn)畫(huà)個(gè)流程圖

可以看到,連接過(guò)程中沒(méi)有看到有解析這一步,因?yàn)榻馕龅臅r(shí)機(jī)并不是固定的,解析的邏輯則是交給了linkResolve這個(gè)類(lèi)來(lái)完成,后文會(huì)進(jìn)一步分析,這里先重點(diǎn)搞清楚 初始化vtable和itable這一步,vatable也被稱(chēng)為虛方法表,從代碼來(lái)看它初始化的邏輯,這也是java是實(shí)現(xiàn)重寫(xiě)的一個(gè)很重要的步驟
虛方法表 & 重寫(xiě)(源碼分析)
void klassVtable::initialize_vtable(bool checkconstraints, TRAPS) {
// Note: Arrays can have intermediate array supers. Use java_super to skip them.
KlassHandle super (THREAD, klass()->java_super());
int nofNewEntries = 0;
if (PrintVtables && !klass()->oop_is_array()) {
ResourceMark rm(THREAD);
tty->print_cr("Initializing: %s", _klass->name()->as_C_string());
}
#ifdef ASSERT
oop* end_of_obj = (oop*)_klass() + _klass()->size();
oop* end_of_vtable = (oop*)&table()[_length];
assert(end_of_vtable <= end_of_obj, "vtable extends beyond end");
#endif
if (Universe::is_bootstrapping()) {
// just clear everything
for (int i = 0; i < _length; i++) table()[i].clear();
return;
}
int super_vtable_len = initialize_from_super(super);
if (klass()->oop_is_array()) {
assert(super_vtable_len == _length, "arrays shouldn't introduce new methods");
} else {
assert(_klass->oop_is_instance(), "must be InstanceKlass");
Array<Method*>* methods = ik()->methods();
int len = methods->length();
int initialized = super_vtable_len;
// Check each of this class's methods against super;
// if override, replace in copy of super vtable, otherwise append to end
for (int i = 0; i < len; i++) {
// update_inherited_vtable can stop for gc - ensure using handles
HandleMark hm(THREAD);
assert(methods->at(i)->is_method(), "must be a Method*");
methodHandle mh(THREAD, methods->at(i));
bool needs_new_entry = update_inherited_vtable(ik(), mh, super_vtable_len, -1, checkconstraints, CHECK);
if (needs_new_entry) {
put_method_at(mh(), initialized);
mh()->set_vtable_index(initialized); // set primary vtable index
initialized++;
}
}
// update vtable with default_methods
Array<Method*>* default_methods = ik()->default_methods();
if (default_methods != NULL) {
len = default_methods->length();
if (len > 0) {
Array<int>* def_vtable_indices = NULL;
if ((def_vtable_indices = ik()->default_vtable_indices()) == NULL) {
def_vtable_indices = ik()->create_new_default_vtable_indices(len, CHECK);
} else {
assert(def_vtable_indices->length() == len, "reinit vtable len?");
}
for (int i = 0; i < len; i++) {
HandleMark hm(THREAD);
assert(default_methods->at(i)->is_method(), "must be a Method*");
methodHandle mh(THREAD, default_methods->at(i));
bool needs_new_entry = update_inherited_vtable(ik(), mh, super_vtable_len, i, checkconstraints, CHECK);
// needs new entry
if (needs_new_entry) {
put_method_at(mh(), initialized);
def_vtable_indices->at_put(i, initialized); //set vtable index
initialized++;
}
}
}
}
// add miranda methods; it will also return the updated initialized
// Interfaces do not need interface methods in their vtables
// This includes miranda methods and during later processing, default methods
if (!ik()->is_interface()) {
initialized = fill_in_mirandas(initialized);
}
// In class hierarchies where the accessibility is not increasing (i.e., going from private ->
// package_private -> public/protected), the vtable might actually be smaller than our initial
// calculation.
assert(initialized <= _length, "vtable initialization failed");
for(;initialized < _length; initialized++) {
put_method_at(NULL, initialized);
}
NOT_PRODUCT(verify(tty, true));
}
}
// Update child's copy of super vtable for overrides
// OR return true if a new vtable entry is required.
// Only called for InstanceKlass's, i.e. not for arrays
// If that changed, could not use _klass as handle for klass
bool klassVtable::update_inherited_vtable(InstanceKlass* klass, methodHandle target_method,
int super_vtable_len, int default_index,
bool checkconstraints, TRAPS) {
//省略
}
從以上代碼可以看出,剛開(kāi)始子類(lèi)的虛方法表與父類(lèi)的虛方法表一致,個(gè)數(shù)也一樣,然后再對(duì)子類(lèi)的方法進(jìn)行遍歷,通過(guò)調(diào)用update_inherited_vtable函數(shù)判斷方法是否是對(duì)父類(lèi)的重寫(xiě),如果是,就調(diào)用klassVtable::put_method_at(Method* m, int index)函數(shù)進(jìn)行重寫(xiě)操作,更新子類(lèi) vtable 表中指向父類(lèi)方法的指針,使其指向子類(lèi)中該方法的入口地址。 若該方法并不是對(duì)父類(lèi)方法的重寫(xiě),則會(huì)調(diào)用klassVtable::put_method_at(Method* m, int index)函數(shù)向該 Java 類(lèi)的 vtable 中插入一個(gè)新的指針元素,使其指向該方法的入口地址,即增加一個(gè)新的虛函數(shù)地址,這里要注意一點(diǎn),對(duì)于重寫(xiě)的方法,子類(lèi)和父類(lèi)的方法表的索引值是一致的,這個(gè)特性很關(guān)鍵,后文會(huì)進(jìn)行介紹
多態(tài)的實(shí)現(xiàn)(源碼分析)
在連接階段,虛方法表初始化完成,這個(gè)時(shí)候,再來(lái)看重寫(xiě)和多態(tài)是怎么實(shí)現(xiàn)的,順便將解析的流程也擼一遍,以下是一個(gè)簡(jiǎn)單的例子,子類(lèi)Son3重寫(xiě)了sayHi()方法,并定義了一個(gè)成員方法用到了多態(tài)的特性
public class Parent3 {
public void sayHi(){
System.out.println("hi,son");
}
}
class Son3 extends Parent3{
public void sayHi(){
System.out.println("hi,parent");
}
public void sayHiTest(){
Parent3 son3 = new Son3();
son3.sayHi();
}
}
顯然若是調(diào)用sayHiTest()方法時(shí),打出的應(yīng)該是"hi,parent",那么,jvm是如何是實(shí)現(xiàn)多態(tài)的呢,首先可以畫(huà)出兩個(gè)類(lèi)的虛方法表

java -v Son3.class一下,
public void sayHi();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String hi,parent
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 9: 0
line 10: 8
public void sayHiTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=1
0: new #5 // class Son3
3: dup
4: invokespecial #6 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #7 // Method Parent3.sayHi:()V
12: return
LineNumberTable:
line 13: 0
line 14: 8
line 15: 12
}
可以看到 son3.sayHi(); 被編譯成了
9: invokevirtual #7 // Method Parent3.sayHi:()V
可以看出,編譯器使用了invokevirtual 指令,指向的符號(hào)引用是 Parent3.sayHi:()V
Java 字節(jié)碼中與調(diào)用相關(guān)的指令共有五種。
invokestatic:用于調(diào)用靜態(tài)方法。
invokespecial:用于調(diào)用私有實(shí)例方法、構(gòu)造器,以及使用 super 關(guān)鍵字調(diào)用父類(lèi)的實(shí)例方法或構(gòu)造器,和所實(shí)現(xiàn)接口的默認(rèn)方法。
invokevirtual:用于調(diào)用非私有實(shí)例方法。
invokeinterface:用于調(diào)用接口方法。
invokedynamic:用于調(diào)用動(dòng)態(tài)方法。
invokevirtual較為復(fù)雜,本文以 invoke_virtual 指令為例,分析 HotSpot JVM 解釋器如何從符號(hào)引用解析出直接引用信息。上文提到,解析的邏輯則是交給了linkResolve這個(gè)類(lèi)來(lái)完成,追一下它的源碼:
void LinkResolver::resolve_virtual_call(CallInfo& result, Handle recv, KlassHandle receiver_klass, KlassHandle resolved_klass,
Symbol* method_name, Symbol* method_signature, KlassHandle current_klass,
bool check_access, bool check_null_and_abstract, TRAPS) {
methodHandle resolved_method;
linktime_resolve_virtual_method(resolved_method, resolved_klass, method_name, method_signature, current_klass, check_access, CHECK);
runtime_resolve_virtual_method(result, resolved_method, resolved_klass, recv, receiver_klass, check_null_and_abstract, CHECK);
}
這段解析邏輯用來(lái)解析下invokevirtual指令對(duì)應(yīng)的符號(hào)引用,在本例中,就是將Method Parent3.sayHi:()V轉(zhuǎn)化為直接引用
可以看到,依次調(diào)用了鏈接時(shí)和運(yùn)行時(shí)的解析方法,以下分別進(jìn)行分析:
連接時(shí)解析方法:
void LinkResolver::linktime_resolve_virtual_method(methodHandle &resolved_method, KlassHandle resolved_klass,
Symbol* method_name, Symbol* method_signature,
KlassHandle current_klass, bool check_access, TRAPS) {
// normal method resolution
resolve_method(resolved_method, resolved_klass, method_name, method_signature, current_klass, check_access, true, CHECK);
assert(resolved_method->name() != vmSymbols::object_initializer_name(), "should have been checked in verifier");
assert(resolved_method->name() != vmSymbols::class_initializer_name (), "should have been checked in verifier");
// check if private interface method
if (resolved_klass->is_interface() && resolved_method->is_private()) {
//拋出異常
}
// check if not static
if (resolved_method->is_static()) {
////拋出異常
}
//省略
}
除去一些校驗(yàn)邏輯,關(guān)注resolve_method這個(gè)方法
methodHandle LinkResolver::resolve_method(const LinkInfo& link_info,
Bytecodes::Code code, TRAPS) {
Handle nested_exception;
KlassHandle resolved_klass = link_info.resolved_klass();
// 1. For invokevirtual, cannot call an interface method
...
// 2. check constant pool tag for called method - must be JVM_CONSTANT_Methodref
...
// 3. lookup method in resolved klass and its super klasses
methodHandle resolved_method = lookup_method_in_klasses(link_info, true, false, CHECK_NULL);
// 4. lookup method in all the interfaces implemented by the resolved klass
if (resolved_method.is_null() && !resolved_klass->is_array_klass()) { // not found in the class hierarchy
resolved_method = lookup_method_in_interfaces(link_info, CHECK_NULL);
if (resolved_method.is_null()) {
// JSR 292: see if this is an implicitly generated method MethodHandle.linkToVirtual(*...), etc
resolved_method = lookup_polymorphic_method(link_info, (Handle*)NULL, (Handle*)NULL, THREAD);
if (HAS_PENDING_EXCEPTION) {
nested_exception = Handle(THREAD, PENDING_EXCEPTION);
CLEAR_PENDING_EXCEPTION;
}
}
}
// 5. method lookup failed
...
// 6. access checks, access checking may be turned off when calling from within the VM.
...
return resolved_method;
}
即先在本類(lèi)中根據(jù)符號(hào)引用來(lái)找到匹配的方法,如果找不到,就去父類(lèi)中找,還找不到,就去實(shí)現(xiàn)的接口中找(注意,這里的類(lèi)指的是字節(jié)碼中的類(lèi)引用,并未到運(yùn)行時(shí)解析的環(huán)節(jié),即本例中的Parent3,而非Son3),尋找方法的這個(gè)邏輯在在InstanceKlass::find_method_index中
int InstanceKlass::find_method_index(const Array<Method*>* methods,
const Symbol* name,
const Symbol* signature,
OverpassLookupMode overpass_mode,
StaticLookupMode static_mode,
PrivateLookupMode private_mode) {
const bool skipping_overpass = (overpass_mode == skip_overpass);
const bool skipping_static = (static_mode == skip_static);
const bool skipping_private = (private_mode == skip_private);
const int hit = binary_search(methods, name);
if (hit != -1) {
const Method* const m = methods->at(hit);
// Do linear search to find matching signature. First, quick check
// for common case, ignoring overpasses if requested.
if (method_matches(m, signature, skipping_overpass, skipping_static, skipping_private)) {
return hit;
}
// search downwards through overloaded methods
int i;
for (i = hit - 1; i >= 0; --i) {
const Method* const m = methods->at(i);
assert(m->is_method(), "must be method");
if (m->name() != name) {
break;
}
if (method_matches(m, signature, skipping_overpass, skipping_static, skipping_private)) {
return i;
}
}
// search upwards
for (i = hit + 1; i < methods->length(); ++i) {
const Method* const m = methods->at(i);
assert(m->is_method(), "must be method");
if (m->name() != name) {
break;
}
if (method_matches(m, signature, skipping_overpass, skipping_static, skipping_private)) {
return i;
}
}
// not found
#ifdef ASSERT
const int index = (skipping_overpass || skipping_static || skipping_private) ? -1 :
linear_search(methods, name, signature);
assert(-1 == index, "binary search should have found entry %d", index);
#endif
}
return -1;
}
邏輯比較清晰,就是遍歷類(lèi)的方法列表,根據(jù)符號(hào)引用來(lái)找到匹配的方法,并返回它的直接引用,既然已經(jīng)解析到了方法的直接引用。上面看到運(yùn)行時(shí)的解析方法又是做什么的呢?
public void sayHiTest(){
Parent3 son3 = new Son3();
son3.sayHi();
}
本例中,通過(guò)連接時(shí)解析,將方法符號(hào)Method Parent3.sayHi:()V轉(zhuǎn)換為了指向該方法的直接引用,但是,根據(jù)java語(yǔ)義,我們知道,真正執(zhí)行的應(yīng)該是Son3重寫(xiě)的方法,那么,這就是 運(yùn)行時(shí)解析 需要處理的邏輯
運(yùn)行時(shí)解析方法:
void LinkResolver::runtime_resolve_virtual_method(CallInfo& result,
const methodHandle& resolved_method,
KlassHandle resolved_klass,
Handle recv,
KlassHandle recv_klass,
bool check_null_and_abstract,
TRAPS) {
// setup default return values
int vtable_index = Method::invalid_vtable_index;
methodHandle selected_method;
...
// do lookup based on receiver klass using the vtable index
if (resolved_method->method_holder()->is_interface()) { // default or miranda method
vtable_index = vtable_index_of_interface_method(resolved_klass,
resolved_method);
assert(vtable_index >= 0 , "we should have valid vtable index at this point");
selected_method = methodHandle(THREAD, recv_klass->method_at_vtable(vtable_index));
} else {
// at this point we are sure that resolved_method is virtual and not
// a default or miranda method; therefore, it must have a valid vtable index.
assert(!resolved_method->has_itable_index(), "");
vtable_index = resolved_method->vtable_index();
// We could get a negative vtable_index for final methods,
// because as an optimization they are they are never put in the vtable,
// unless they override an existing method.
// If we do get a negative, it means the resolved method is the the selected
// method, and it can never be changed by an override.
if (vtable_index == Method::nonvirtual_vtable_index) {
assert(resolved_method->can_be_statically_bound(), "cannot override this method");
selected_method = resolved_method;
} else {
selected_method = methodHandle(THREAD, recv_klass->method_at_vtable(vtable_index));
}
}
...
// setup result
result.set_virtual(resolved_klass, recv_klass, resolved_method, selected_method, vtable_index, CHECK);
}
尤其注意這行代碼:
selected_method = methodHandle(THREAD, recv_klass->method_at_vtable(vtable_index));
通過(guò)源碼上下文可以看出,jvm可以獲取到調(diào)用invokevirtual時(shí)調(diào)用者的實(shí)際類(lèi)型,即本例中的Son3,本行代碼中recv_klass就是Son3類(lèi)的指向instanceKlass的指針,調(diào)用其method_at_vtable方法即可獲取Son3對(duì)應(yīng)虛方法表中特定索引值的直接引用, 那么jvm如何知道該方法的索引值呢
在連接時(shí)解析階段,就已經(jīng)獲得了父類(lèi)直接引用,讀取其對(duì)應(yīng)的虛表索引值(vtable_index),對(duì)于重寫(xiě)方法,父子類(lèi)的索引值相同,即可直接通method_at_vtable(vtable_index) 方法獲取子類(lèi)方法的直接引用,流程如下:

至此,java重寫(xiě)、多態(tài)語(yǔ)義在jvm中的大體實(shí)現(xiàn)邏輯已經(jīng)分析完畢,接下來(lái)分析java的另外幾個(gè)語(yǔ)義:重載、隱藏
重載語(yǔ)義
重載相對(duì)于重寫(xiě)而言較為簡(jiǎn)單,其實(shí)現(xiàn)主要在于編譯階段,看下面這個(gè)例子:
public class Parent2 {
public void sayHi(String string){
System.out.println("hi,string");
}
public void sayHi(Object object){
System.out.println("hi,object");
}
public static void main(String[] args) {
new Parent2().sayHi("hi");
}
}
void sayHi(String string)和 void sayHi(Object object)構(gòu)成重載,看下字節(jié)碼中的區(qū)別:
public void sayHi(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC
...
public void sayHi(java.lang.Object);
descriptor: (Ljava/lang/Object;)V
flags: ACC_PUBLIC
...
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: new #6 // class Parent2
3: dup
4: invokespecial #7 // Method "<init>":()V
7: ldc #8 // String hi
9: invokevirtual #9 // Method sayHi:(Ljava/lang/String;)V
12: return
二者的描述符就不相同,由于二者區(qū)分在編譯階段已經(jīng)完成,我們可以認(rèn)為 Java 虛擬機(jī)不存在重載這一概念。因此,在某些文章中,重載也被稱(chēng)為靜態(tài)綁定(static binding),把重寫(xiě)被稱(chēng)為動(dòng)態(tài)綁定(dynamic binding)
繼續(xù)看這個(gè)例子,調(diào)用方法時(shí)傳入的參數(shù)為"hi",既是String類(lèi)型,也是Object類(lèi)型,但是在main()函數(shù)對(duì)應(yīng)的字節(jié)碼中,直接認(rèn)定了調(diào)用(Ljava/lang/String;)V,這是為什么呢
Java 編譯器選取重載方法的過(guò)程共分為三個(gè)階段:
1、在不考慮對(duì)基本類(lèi)型自動(dòng)裝拆箱(auto-boxing,auto-unboxing),以及可變長(zhǎng)參數(shù)的情況下選取重載方法;
2、如果在第 1 個(gè)階段中沒(méi)有找到適配的方法,那么在允許自動(dòng)裝拆箱,但不允許可變長(zhǎng)參數(shù)的情況下選取重載方法;
3、如果在第 2 個(gè)階段中沒(méi)有找到適配的方法,那么在允許自動(dòng)裝拆箱以及可變長(zhǎng)參數(shù)的情況下選取重載方法。
如果 Java 編譯器在同一個(gè)階段中找到了多個(gè)適配的方法,那么它會(huì)在其中選擇一個(gè)最為貼切的,而決定貼切程度的一個(gè)關(guān)鍵就是形式參數(shù)類(lèi)型的繼承關(guān)系。
本例中,String和Object均符合,于是java編譯器選擇了最為貼切的Ljava/lang/String;)V方法
再看隱藏:
隱藏語(yǔ)義
public class Parent4 {
public static void sayHi(){
System.out.println("hi,son");
}
}
class Son4 extends Parent4{
public static void sayHi(){
System.out.println("hi,parent");
}
public static void main(String[] args) {
Parent4 son4 = new Son4();
son4.sayHi();
}
}
本例中,將打印出"hi,son",這和上面多態(tài)的場(chǎng)景基本一致,區(qū)別在于本例中的方法是靜態(tài)方法,所以盡管真正的實(shí)例是Son4類(lèi)型的,但最后還是調(diào)用的父類(lèi)的方法,jvm是如何處理這個(gè)邏輯的,首先看下字節(jié)碼:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #5 // class Son4
3: dup
4: invokespecial #6 // Method "<init>":()V
7: astore_1
8: aload_1
9: pop
10: invokestatic #7 // Method Parent4.sayHi:()V
13: return
可以看到,方法調(diào)用被編譯成了:
10: invokestatic #7 // Method Parent4.sayHi:()V
看看hospot源碼中是如何解析它的:
void LinkResolver::resolve_static_call(CallInfo& result, KlassHandle& resolved_klass, Symbol* method_name,
Symbol* method_signature, KlassHandle current_klass,
bool check_access, bool initialize_class, TRAPS) {
methodHandle resolved_method;
linktime_resolve_static_method(resolved_method, resolved_klass, method_name, method_signature, current_klass, check_access, CHECK);
resolved_klass = KlassHandle(THREAD, resolved_method->method_holder());
// Initialize klass (this should only happen if everything is ok)
if (initialize_class && resolved_klass->should_be_initialized()) {
resolved_klass->initialize(CHECK);
linktime_resolve_static_method(resolved_method, resolved_klass, method_name, method_signature, current_klass, check_access, CHECK);
}
// setup result
result.set_static(resolved_klass, resolved_method, CHECK);
}
可以看到,與上文中的resolve_virtual_call相比,解析過(guò)程只有連接時(shí)解析(linktime_resolve_static_method),缺少了運(yùn)行時(shí)解析,連接時(shí)解析的邏輯基本一致,而從上文的分析中,連接時(shí)解析 只會(huì)按照字節(jié)碼中的符號(hào)引用來(lái)進(jìn)行解析,自然,本例的sayHi:()方法最后解析出來(lái)的是方法Parent4.sayHi:()V的直接引用,調(diào)用的是Parent4.sayHi()方法,表現(xiàn)出來(lái)的現(xiàn)象就是子類(lèi)的方法隱藏
遮蔽語(yǔ)義
下面這段代碼,在init()函數(shù)中定義了一個(gè)與類(lèi)變量同名的局部變量a
public class Parent5 {
public String a = "out";
public void init(){
String a = "in";
System.out.println(a);
System.out.println(this.a);
}
public static void main(String[] args){
new Parent5().init();
}
}
本例中,a和this.a的值是不同的,當(dāng)使用簡(jiǎn)單名a的時(shí)候,值是“in”,先看看字節(jié)碼:
常量池(部分)
Constant pool:
#1 = Methodref #10.#22 // java/lang/Object."<init>":()V
#2 = String #23 // out
#3 = Fieldref #7.#24 // Parent5.a:Ljava/lang/String;
#4 = String #25 // in
#5 = Fieldref #26.#27 // java/lang/System.out:Ljava/io/PrintStream;
#6 = Methodref #28.#29 // java/io/PrintStream.println:(Ljava/lang/String;)V
#7 = Class #30 // Parent5
#8 = Methodref #7.#22 // Parent5."<init>":()V
#9 = Methodref #7.#31 // Parent5.init:()V
#10 = Class #32 // java/lang/Object
init()函數(shù)對(duì)應(yīng)的字節(jié)碼
public void init();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=1
0: ldc #4 // String in
2: astore_1
3: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
6: aload_1
7: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
13: aload_0
14: getfield #3 // Field a:Ljava/lang/String;
17: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: return
首先看字節(jié)碼中是怎么描述類(lèi)變量a的,
3 = Fieldref #7.#24 // Parent5.a:Ljava/lang/String;
7 = Class #30 // Parent5
24 = NameAndType #11:#12 // a:Ljava/lang/String;
11 = Utf8 a
12 = Utf8 Ljava/lang/String;
常量池中有個(gè)Filedrdf類(lèi)型的符號(hào)引用#3,名稱(chēng)和類(lèi)型在#24常量中存放,拼起來(lái)就是 Parent5.a:Ljava/lang/String;
再看 System.out.println(this.a);這條語(yǔ)句被編譯成了什么:
14: getfield #3 // Field a:Ljava/lang/String;
17: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
即先根據(jù)常量池中的符號(hào)引用#3獲取值(Field a:Ljava/lang/String),然后調(diào)用打印函數(shù),顯然直接指向了類(lèi)變量a
再看局部變量a在字節(jié)碼中如何表示:
#4 = String #25 // in
#25 = Utf8 in
常量池中通過(guò)這兩個(gè)常量就完成了對(duì)局部變量a的表示,仔細(xì)一看,沒(méi)有變量名,再看System.out.println(a);這條語(yǔ)句是如何讀取變量的:
0: ldc #4 // String in
2: astore_1
3: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
6: aload_1
7: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
直接通過(guò)ldc指令將常量池中的常量#4壓到棧頂,即字符串“in”,astore_1指令將“in”字符串的引用保存在局部變量表中,aload_1指令從局部變量表裝載入“in”字符串的引用到操作數(shù)棧的棧頂,然后執(zhí)行println方法
可以看出,整個(gè)過(guò)程中,并沒(méi)有體現(xiàn)出局部變量a的變量名,自然,盡管在代碼中,類(lèi)變量a和局部變量有著一樣的變量名和類(lèi)型,但是經(jīng)過(guò)編譯后,對(duì)于jvm而言,二者根本不會(huì)產(chǎn)生任何混淆, 甚至如果將局部變量a換成局部變量b,編譯出來(lái)的字節(jié)碼一模一樣
本例就是遮蔽的一種典型場(chǎng)景,可以看出,該語(yǔ)義的實(shí)現(xiàn)在編譯期間就已經(jīng)完成了