從jvm虛擬機角度看Java多態 ->(重寫override)的實現原理

從jvm虛擬機角度看Java多態 ->(重寫override)的實現原理

工具與環境:

Windows 7 x64企業版

Cygwin x64

jdk1.8.0_162

openjdk-8u40-src-b25-10_feb_2015

Vs2010 professional

0x00 Java多態簡單介紹

1、多態的概念:

JAVA類被jvm加載運行時根據調用該方法的對像實例的類型來決定選擇調用哪個方法則被稱為運行時多態。也叫動態綁定:是指在執行期間判斷所引用對象實例的實際類型,根據其實際的類型調用其相應的方法。

2、多態的優點:

a.可替換性: 多態對已存在代碼具有可替換性。

b.可擴充性: 多態對代碼具有可擴充性。

c.靈活性: 它在應用中體現了靈活多樣的操作,提高了使用效率。

d.簡化性: 多態簡化對應用軟件的代碼編寫和修改過程,尤其在處理大量對象的運算和操作時,這個特點尤為突出和重要。

3、示例代碼(以下分析基於該代碼):

public class Animal{

public void say(){

System.out.println("is animal");

}

public static void main(String[] args){

Animal animal = new Dog();

run(animal);

animal = new Cat();

run(animal);

}

public static void run(Animal animal){

animal.say();

}

}

class Dog extends Animal{

public void say(){

System.out.println("is Dog");

}

}

class Cat extends Animal{

public void say(){

System.out.println("is Cat");

}

}

編譯: javac Animal.java生成.class文件。

運行後如下圖:

從jvm虛擬機角度看Java多態 ->(重寫override)的實現原理

4、上面示例程序中定義了類Animal ,同時定義了 2 個子類 Dog 和 Cat,這 2 個子類都重寫了基類中的 say()方法 。 在 main()函數中,將 animal 實例引用分別指向 Dog 和 Cat 的實例, 並分別調用 run(Animal)方法。 在本示例中,當在 Animal.run(Animal)方法中執行 animal.say()時, 因為在編譯期並不知道 animal 這個引用到底指向哪個實例對象,所以編譯期無法進行綁定,必須等到運行期才能確切知道最終調用哪個子類的 say()方法,這便是動態綁定,也即晚綁定,這是 Java語言以及絕大多數面嚮對象語言的動態機制最直接的體現。

0x01: C++的多態與vftable分析

1. 在分析JVM多態的實現原理之前,我們先一起看看 C++中虛方法表的實現機制,這兩者有很緊密的聯繫,有助於我們理解JVM中的多態機制。

2. 示例代碼:

class Cpuls{

public:

int x;

public:

void func(){

this->x = 2;

}

};

int main(){

Cpuls cplus;

return 0;

}

這個 C++示例很簡單,類中包含一個 int 類型的變量和一個 run()方法,在 main函數中定義一個Cpuls對像。通過vs 調試時看內存情況,沒有虛函數時對象的內存表現如下圖:

從jvm虛擬機角度看Java多態 ->(重寫override)的實現原理

由於 CPLUS 類中僅包含 l 個 int 類型的變量 ,因此觀察結果中的 cplus 實例內存地址,只有變量 x 。

現在將 C++類中的 run方法修改一下,變成虛方法,在觀察對象的內存表現:

從jvm虛擬機角度看Java多態 ->(重寫override)的實現原理

注意看,現在的值變了,cplus實例首地址不是其變量x了,而是一個vfable,這就是虛表,並且vfable中存放加了virtual關鍵字的虛函數func函數的地址,這是因為當C++類中出現虛方法時,表示該方法擁有多態性,此時會根據類型指針所指向的實際對象而在運行期調用不同的方法。

C++為了實現多態,就在 C++類實例對象中嵌入虛函數表vfable ,通過虛函數表來實現運行期的方法分派 。 C++中所謂虛函數表,其實就是一個普通的表,表中存儲的是方法指針, 方法指針會指向目標方法的內存地址,所以虛函數表就是一堆指針的集合而已。

詳細的可以看這位大牛的分析https://bbs.pediy.com/thread-221160.htm

0x02: JVM中函數重寫實現機制

1. Java中的多態在語義上與上面分析C++的原理是相同的,Java在JVM中的多態機制並沒有跳出這個圈也採用了 vftable 來實現動態綁定。

JVM的 vftable 機制與 C++的 vftable機制之間的不同點在於, C++的 vftable

在編譯期間便由編譯器完成分析和模型構建,而 JVM 的 vftable 則在 JVM

運行期類被加載時進行動態構建。下面通過hotspot源碼來分析JVM中函數重寫機制。

2. 當我們通過java 執行class文件時,JVM 會在第一次加載類時調用classFileParser.cpp::parseClassFile()函數對 Java class 文件字節碼進行解析,在parseClassFile()函數中會調用parse_methods()函數解析class文件類中的方法,parse_methods()函數執行完之後,會繼續調用

klassVtable::compute_vtable_size_and_num_mirandas()函數,計算當前類的vtable大小,下面看看該方法實現的主要邏輯:

判斷是否有虛函數,如果有就將個數增加, src\share\vm\oops\klassVtable.cpp

void klassVtable::compute_vtable_size_and_num_mirandas(

int* vtable_length_ret, int* num_new_mirandas,

GrowableArray* all_mirandas, Klass* super,

Array* methods, AccessFlags class_flags,

Handle classloader, Symbol* classname, Array* local_interfaces,

TRAPS) {

No_Safepoint_Verifier nsv;

// set up default result values

int vtable_length = 0;

// start off with super's vtable length

InstanceKlass* sk = (InstanceKlass*)super;

//獲取父類 vtable 的大小,並將當前類的 vtable 的大小設置為父類 vtable 的大小。

vtable_length = super == NULL ? 0 : sk->vtable_length();

// go thru each method in the methods table to see if it needs a new entry

int len = methods->length();//方法個數

for (int i = 0; i < len; i++) {

assert(methods->at(i)->is_method(), "must be a Method*");

methodHandle mh(THREAD, methods->at(i));

/*循環遍歷當前 Java 類的每一個方法 ,調用 needs_new_vtable_entry()函數進行判斷,

如果判斷的結果是 true ,則將 vtable 的大小增 1 */

if (needs_new_vtable_entry(mh, super, classloader, classname, class_flags, THREAD)) {

vtable_length += vtableEntry::size(); // we need a new entry

}

}

GrowableArray new_mirandas(20);

// compute the number of mirandas methods that must be added to the end

get_mirandas(&new_mirandas, all_mirandas, super, methods, NULL, local_interfaces);

*num_new_mirandas = new_mirandas.length();

// Interfaces do not need interface methods in their vtables

// This includes miranda methods and during later processing, default methods

if (!class_flags.is_interface()) {

vtable_length += *num_new_mirandas * vtableEntry::size();

}

if (Universe::is_bootstrapping() && vtable_length == 0) {

// array classes don't have their superclass set correctly during

// bootstrapping

vtable_length = Universe::base_vtable_size();

}

if (super == NULL && !Universe::is_bootstrapping() &&

vtable_length != Universe::base_vtable_size()) {

// Someone is attempting to redefine java.lang.Object incorrectly. The

// only way this should happen is from

// SystemDictionary::resolve_from_stream(), which will detect this later

// and throw a security exception. So don't assert here to let

// the exception occur.

vtable_length = Universe::base_vtable_size();

}

assert(super != NULL || vtable_length == Universe::base_vtable_size(),

"bad vtable size for class Object");

assert(vtable_length % vtableEntry::size() == 0, "bad vtable length");

assert(vtable_length >= Universe::base_vtable_size(), "vtable too small");

*vtable_length_ret = vtable_length;//返回虛方法個數

}

上面這段代碼計算 vftable 個數的思路主要分為兩步 :

a:獲取父類 vftable 的個數,並將當前類的 vftable 的個數設置為父類 vftable 的個數。

b:循環遍歷當前 Java 類的每一個方法 ,調用 needs_new_vtable_entry()函數進行判斷,如果判斷的結果是 true ,則將 vftable 的個數增 1 。

3. 現在看needs_new_vtable_entry()函數是如何判斷虛函數的,判斷條件是什麼?

bool klassVtable::needs_new_vtable_entry(methodHandle target_method,

Klass* super,

Handle classloader,

Symbol* classname,

AccessFlags class_flags,

TRAPS) {

/*如果 Java 方法被 final、static修飾,或者 Java 類被 final 修飾,或者 Java 方法是構造

函數,則返回 false */

if (class_flags.is_interface()) {

// Interfaces do not use vtables, except for java.lang.Object methods,

// so there is no point to assigning

// a vtable index to any of their local methods. If we refrain from doing this,

// we can use Method::_vtable_index to hold the itable index

return false;

}

if (target_method->is_final_method(class_flags) ||

// a final method never needs a new entry; final methods can be statically

// resolved and they have to be present in the vtable only if they override

// a super's method, in which case they re-use its entry

(target_method()->is_static()) ||

// static methods don't need to be in vtable

(target_method()->name() == vmSymbols::object_initializer_name())

// is never called dynamically-bound

) {

return false;

}

// Concrete interface methods do not need new entries, they override

// abstract method entries using default inheritance rules

if (target_method()->method_holder() != NULL &&

target_method()->method_holder()->is_interface() &&

!target_method()->is_abstract() ) {

return false;

}

// we need a new entry if there is no superclass

if (super == NULL) {

return true;

}

// private methods in classes always have a new entry in the vtable

// specification interpretation since classic has

// private methods not overriding

// JDK8 adds private methods in interfaces which require invokespecial

if (target_method()->is_private()) {

return true;

}

// Package private methods always need a new entry to root their own

// overriding. This allows transitive overriding to work.

if (target_method()->is_package_private()) {

return true;

}

// search through the super class hierarchy to see if we need

// a new entry

/*遍歷父類中同名 、簽名也完全相同的方法,如果父類方法的訪問權限是 public 或者 protected,

並且沒有 static 或 private 修飾,則說明子類重寫了父類的方法,此時返回 false*/

ResourceMark rm;

Symbol* name = target_method()->name();//當前方法名

Symbol* signature = target_method()->signature();

Klass* k = super;

Method* super_method = NULL;

InstanceKlass *holder = NULL;

Method* recheck_method = NULL;

while (k != NULL) {

//test

//printf("class name = %s\n",classname->base());

// lookup through the hierarchy for a method with matching name and sign.

super_method = InstanceKlass::cast(k)->lookup_method(name, signature);//判斷當前方法名與簽名在父類中是否吸同名同簽名的方法

if (super_method == NULL) {

break; // we still have to search for a matching miranda method

}

// get the class holding the matching method

// make sure you use that class for is_override

InstanceKlass* superk = super_method->method_holder();

// we want only instance method matches

// pretend private methods are not in the super vtable

// since we do override around them: e.g. a.m pub/b.m private/c.m pub,

// ignore private, c.m pub does override a.m pub

// For classes that were not javac'd together, we also do transitive overriding around

// methods that have less accessibility

if ((!super_method->is_static()) &&

(!super_method->is_private())) {

if (superk->is_override(super_method, classloader, classname, THREAD)) {//如果父類方法的訪問權限是public或者protected,並且沒有static或private修飾,則說明子類重寫了父類的方法,此時返回false

return false;

// else keep looking for transitive overrides

}

}

// Start with lookup result and continue to search up

k = superk->super(); // haven't found an override match yet; continue to look

}

// if the target method is public or protected it may have a matching

// miranda method in the super, whose entry it should re-use.

// Actually, to handle cases that javac would not generate, we need

// this check for all access permissions.

InstanceKlass *sk = InstanceKlass::cast(super);

if (sk->has_miranda_methods()) {

if (sk->lookup_method_in_all_interfaces(name, signature, Klass::normal) != NULL) {

return false; // found a matching miranda; we do not need a new entry

}

}

return true; // found no match; we need a new entry

}

上面代碼主要判斷Java 類在運行期進行動態綁定的方法,一定會被聲明為 public 或者 protected 的,並且沒有 static 和 final 修飾,且 Java 類上也沒有 final 修飾 。

4. 當class文件被分析完成後就要創建一個內存中的instanceKlass對象來存放class信息,這時就要用到上面分析的虛表個數了vtable_size。該變量值將在創建類所對應的instanceKlass對象時被保存到該對象中的一vtable_Ien字段中。

// We can now create the basic Klass* for this klass

_klass = InstanceKlass::allocate_instance_klass(loader_data,

vtable_size,

itable_size,

info.static_field_size,

total_oop_map_size2,

rt,

access_flags,

name,

super_klass(),

!host_klass.is_null(),

CHECK_(nullHandle));

5. 當class分析並將相關的信息存放在instanceKlass實例對像中後就準備要執行函數了, 在分析重寫之前我們來看看vtable在什麼地方。

\src\share\vm\oops\instanceKlass.cpp

link_class->link_class_impl

//初始化虛表

// Initialize the vtable and interface table after

// methods have been rewritten since rewrite may

// fabricate new Method*s.

// also does loader constraint checking

if (!this_oop()->is_shared()) {

ResourceMark rm(THREAD);

this_oop->vtable()->initialize_vtable(true, CHECK_false);//初始化虛表

this_oop->itable()->initialize_itable(true, CHECK_false);

}

每一個 Java 類在 JVM 內部都有一個對應的instanceKlass, vtable 就被分配在這個 oop 內存區域的後面。

inline InstanceKlass* klassVtable::ik() const {

Klass* k = _klass();

assert(k->oop_is_instance(), "not an InstanceKlass");

return (InstanceKlass*)k;

}

klassVtable(KlassHandle h_klass, void* base, int length) : _klass(h_klass) {

_tableOffset = (address)base - (address)h_klass(); _length = length;//虛表偏移(InstanceKlass對像大小)

}

//虛表地址

vtableEntry* table() const {

return (vtableEntry*)(address(_klass()) + _tableOffset); //InstanceKlass對像基址加上InstanceKlass對像大小

}

instanceKlass大小在 windows64系統的大小為0x1b8如下,後面用hsdb查看vtable時會用到。

從jvm虛擬機角度看Java多態 ->(重寫override)的實現原理

6. 方法的重寫主要在該函數中:

klassVtable::initialize_vtable(bool checkconstraints, TRAPS)函數主要邏輯:

int klassVtable::initialize_from_super(KlassHandle super) {

if (super.is_null()) {

return 0;

} else {

// copy methods from superKlass

// can't inherit from array class, so must be InstanceKlass

assert(super->oop_is_instance(), "must be instance klass");

InstanceKlass* sk = (InstanceKlass*)super();

klassVtable* superVtable = sk->vtable();

assert(superVtable->length() <= _length, "vtable too short");

#ifdef ASSERT

superVtable->verify(tty, true);

#endif

superVtable->copy_vtable_to(table());

#ifndef PRODUCT

if (PrintVtables && Verbose) {

ResourceMark rm;

tty->print_cr("copy vtable from %s to %s size %d", sk->internal_name(), klass()->internal_name(), _length);

}

#endif

return superVtable->length();

}

}

//

// Revised lookup semantics introduced 1.3 (Kestrel beta)

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* 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));

/*判斷是否重寫或有虛函數,如果overwrite函數,(方法名字,參數簽名 完全一樣),

也就是替換虛擬表相同順序的內容*/

bool needs_new_entry = update_inherited_vtable(ik(), mh, super_vtable_len, -1, checkconstraints, CHECK);

//needs_new_entry ==true如果符合虛擬函數則順序添加到虛擬表尾部

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* default_methods = ik()->default_methods();

if (default_methods != NULL) {

len = default_methods->length();

if (len > 0) {

Array* 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_inherited_vtable函數判斷子類中是否有與父類中方法名簽名完全相同的方法,若該方法是對父類方法的重寫,就調用klassVtable::put_method_at(Method*m, int index)函數進行重寫操作,更新父類 vtable 表中指向父類被重寫的方法的指針,使其指向子類中該方法的內存地址。

若該方法並不是對父類方法的重寫,則會調用klassVtable::put_method_at(Method* m, int index)函數向該 Java 類的 vtable 中插入一個新的指針元素,使其指向該方法的內存地址,增加一個新的虛函數地址。

bool klassVtable::update_inherited_vtable(InstanceKlass* klass, methodHandle target_method,

int super_vtable_len, int default_index,

bool checkconstraints, TRAPS) {

ResourceMark rm;

bool allocate_new = true;

assert(klass->oop_is_instance(), "must be InstanceKlass");

Array* def_vtable_indices = NULL;

bool is_default = false;

// default methods are concrete methods in superinterfaces which are added to the vtable

// with their real method_holder

// Since vtable and itable indices share the same storage, don't touch

// the default method's real vtable/itable index

// default_vtable_indices stores the vtable value relative to this inheritor

if (default_index >= 0 ) {

is_default = true;

def_vtable_indices = klass->default_vtable_indices();

assert(def_vtable_indices != NULL, "def vtable alloc?");

assert(default_index <= def_vtable_indices->length(), "def vtable len?");

} else {

assert(klass == target_method()->method_holder(), "caller resp.");

// Initialize the method's vtable index to "nonvirtual".

// If we allocate a vtable entry, we will update it to a non-negative number.

target_method()->set_vtable_index(Method::nonvirtual_vtable_index);

}

// Static and methods are never in

if (target_method()->is_static() || target_method()->name() == vmSymbols::object_initializer_name()) {

return false;

}

if (target_method->is_final_method(klass->access_flags())) {

// a final method never needs a new entry; final methods can be statically

// resolved and they have to be present in the vtable only if they override

// a super's method, in which case they re-use its entry

allocate_new = false;

} else if (klass->is_interface()) {

allocate_new = false; // see note below in needs_new_vtable_entry

// An interface never allocates new vtable slots, only inherits old ones.

// This method will either be assigned its own itable index later,

// or be assigned an inherited vtable index in the loop below.

// default methods inherited by classes store their vtable indices

// in the inheritor's default_vtable_indices

// default methods inherited by interfaces may already have a

// valid itable index, if so, don't change it

// overpass methods in an interface will be assigned an itable index later

// by an inheriting class

if (!is_default || !target_method()->has_itable_index()) {

target_method()->set_vtable_index(Method::pending_itable_index);

}

}

// we need a new entry if there is no superclass

if (klass->super() == NULL) {

return allocate_new;

}

// private methods in classes always have a new entry in the vtable

// specification interpretation since classic has

// private methods not overriding

// JDK8 adds private methods in interfaces which require invokespecial

if (target_method()->is_private()) {

return allocate_new;

}

// search through the vtable and update overridden entries

// Since check_signature_loaders acquires SystemDictionary_lock

// which can block for gc, once we are in this loop, use handles

// For classfiles built with >= jdk7, we now look for transitive overrides

Symbol* name = target_method()->name();

Symbol* signature = target_method()->signature();

const char* m_method_name = NULL;

m_method_name = name->as_C_string();

if (0 == strcmp(m_method_name, "say"))

{

printf("target_method name %s\n",m_method_name);

}

KlassHandle target_klass(THREAD, target_method()->method_holder());

if (target_klass == NULL) {

target_klass = _klass;

}

Handle target_loader(THREAD, target_klass->class_loader());

Symbol* target_classname = target_klass->name();

const char* class_name = target_classname->as_C_string();

//可以在這裡判斷加載目標類時斷點

if (0 == strcmp(class_name, "Dog") || 0 == strcmp(class_name, "Animal") || 0 == strcmp(class_name, "Cat"))

{

printf("update_inherited_vtable %s\n",class_name);

}

for(int i = 0; i < super_vtable_len; i++) {

Method* super_method = method_at(i);

// Check if method name matches

m_method_name = super_method->name()->as_C_string();

printf("super_method name %s\n",m_method_name);

//判斷方法名簽名是否與父類中相同

if (super_method->name() == name && super_method->signature() == signature) {

// get super_klass for method_holder for the found method

InstanceKlass* super_klass = super_method->method_holder();

//判斷是否為重寫

if (is_default

|| ((super_klass->is_override(super_method, target_loader, target_classname, THREAD))

|| ((klass->major_version() >= VTABLE_TRANSITIVE_OVERRIDE_VERSION)

&& ((super_klass = find_transitive_override(super_klass,

target_method, i, target_loader,

target_classname, THREAD))

!= (InstanceKlass*)NULL))))

{

// Package private methods always need a new entry to root their own

// overriding. They may also override other methods.

if (!target_method()->is_package_private()) {

allocate_new = false;

}

if (checkconstraints) {

// Override vtable entry if passes loader constraint check

// if loader constraint checking requested

// No need to visit his super, since he and his super

// have already made any needed loader constraints.

// Since loader constraints are transitive, it is enough

// to link to the first super, and we get all the others.

Handle super_loader(THREAD, super_klass->class_loader());

if (target_loader() != super_loader()) {

ResourceMark rm(THREAD);

Symbol* failed_type_symbol =

SystemDictionary::check_signature_loaders(signature, target_loader,

super_loader, true,

CHECK_(false));

if (failed_type_symbol != NULL) {

const char* msg = "loader constraint violation: when resolving "

"overridden method "%s" the class loader (instance"

" of %s) of the current class, %s, and its superclass loader "

"(instance of %s), have different Class objects for the type "

"%s used in the signature";

char* sig = target_method()->name_and_sig_as_C_string();

const char* loader1 = SystemDictionary::loader_name(target_loader());

char* current = target_klass->name()->as_C_string();

const char* loader2 = SystemDictionary::loader_name(super_loader());

char* failed_type_name = failed_type_symbol->as_C_string();

size_t buflen = strlen(msg) + strlen(sig) + strlen(loader1) +

strlen(current) + strlen(loader2) + strlen(failed_type_name);

char* buf = NEW_RESOURCE_ARRAY_IN_THREAD(THREAD, char, buflen);

jio_snprintf(buf, buflen, msg, sig, loader1, current, loader2,

failed_type_name);

THROW_MSG_(vmSymbols::java_lang_LinkageError(), buf, false);

}

}

}

put_method_at(target_method(), i);//替換虛函數

if (!is_default) {

target_method()->set_vtable_index(i);

} else {

if (def_vtable_indices != NULL) {

def_vtable_indices->at_put(default_index, i);

}

assert(super_method->is_default_method() || super_method->is_overpass()

|| super_method->is_abstract(), "default override error");

}

#ifndef PRODUCT

if (PrintVtables && Verbose) {

ResourceMark rm(THREAD);

char* sig = target_method()->name_and_sig_as_C_string();

tty->print("overriding with %s::%s index %d, original flags: ",

target_klass->internal_name(), sig, i);

super_method->access_flags().print_on(tty);

if (super_method->is_default_method()) {

tty->print("default ");

}

if (super_method->is_overpass()) {

tty->print("overpass");

}

tty->print("overriders flags: ");

target_method->access_flags().print_on(tty);

if (target_method->is_default_method()) {

tty->print("default ");

}

if (target_method->is_overpass()) {

tty->print("overpass");

}

tty->cr();

}

#endif /*PRODUCT*/

} else {

// allocate_new = true; default. We might override one entry,

// but not override another. Once we override one, not need new

#ifndef PRODUCT

if (PrintVtables && Verbose) {

ResourceMark rm(THREAD);

char* sig = target_method()->name_and_sig_as_C_string();

tty->print("NOT overriding with %s::%s index %d, original flags: ",

target_klass->internal_name(), sig,i);

super_method->access_flags().print_on(tty);

if (super_method->is_default_method()) {

tty->print("default ");

}

if (super_method->is_overpass()) {

tty->print("overpass");

}

tty->print("overriders flags: ");

target_method->access_flags().print_on(tty);

if (target_method->is_default_method()) {

tty->print("default ");

}

if (target_method->is_overpass()) {

tty->print("overpass");

}

tty->cr();

}

#endif /*PRODUCT*/

}

}

}

return allocate_new;//如果沒有與父類中相同的函數並且滿足虛函數特性就返回true

}

void klassVtable::put_method_at(Method* m, int index) {

#ifndef PRODUCT

if (PrintVtables && Verbose) {

ResourceMark rm;

const char* sig = (m != NULL) ? m->name_and_sig_as_C_string() : "";

tty->print("adding %s at index %d, flags: ", sig, index);

if (m != NULL) {

m->access_flags().print_on(tty);

if (m->is_default_method()) {

tty->print("default ");

}

if (m->is_overpass()) {

tty->print("overpass");

}

}

tty->cr();

}

#endif

table()[index].set(m);// 將函數地址放入虛表

}

7. 用上面的Animal文件調試分析,看看vtable內存情況。

Animal 父類

say 0x14890250 index 5

table() 0x14890520 vtableEntry *

table()[index] {_method=0x14890250 } vtableEntry

Dog

say 0x148906e0 index 5

table() 0x14890920 vtableEntry *

table()[index] {_method=0x14890250 } vtableEntry //沒有替換前與Animal中say函數地址相同

table()[index] {_method=0x148906e0 } vtableEntry //替換後為Dog的say函數地址

從jvm虛擬機角度看Java多態 ->(重寫override)的實現原理

當Hotspot在運行期加載類Animal時,其vtable 中將會有一個指針元素指向其say方法在Hotspot內部的內存首地址,當 Hotspot 加載類 Dog 時, 首先類 Dog 完全繼承其父類 Animal 的 vtable,因此類 Dog 便也有一個 vtable ,並且 vtable 裡有一個指針指向類 Animal 的 say方法的內存地址 。

Hotspot 遍歷類 Dog 的所有方法,並發現say方法是 public 的,並且沒有被

static 、 final 修飾,於是 HotSpot 去搜索其父類中名稱相同、簽名也相同的方法,結果發現父類中存在一個完全一樣的方法,於是 HotSpot就會將類 Dog 的 vtable 中原本指向類 Animal 的 say方法的內存地址的指針值修改成指向類Dog 自己的say方法所在的內存地址 。

0x03: HSDB 查看java 類中的 vtable

1. 下面我們將通過hsdb來驗證前面的分析。如前面所述,Java 類在 JVM 內部所對應的類型是 instanceKlass,根據上面一節的分析,我們知道vtable 便分配在 instanceKlass 對象實例的內存末尾 。

instanceKlass 對象實例在X64平臺上內存中所佔內存大小是 Oxlb8 字節(32位平臺上

sizeof(InstanceKlass)=0x00000108),換算成十進制是 440。 根據這個特點,可以使用 HSDB獲取到 Java 類所對應的 instanceKlass 在內存中的首地址,然後加上 Oxlb8 ,就得到 vtable 的內存地址 ,如此便可以查看這個內存位置上的 vtable 成員數據 。

還是用Animal文件做示例,類 Animal 中僅包含 1 個 Java 方法 ,因此類 Animal 的 vtable長度一共是 6 ,另外 5 個是超類 java.lang.Object 中的5個方法。使用JDB 調試(jdb -XX:-UseCompressedOops Animal),並運行至斷點處使程序暫停(stop in Animal.main)->(run),jps查看ID,然後使用 HSDB 連接上測試程序(java -classpath "%JAVA_HOME%/lib/sa-jdi.jar" sun.jvm.hotspot.HSDB),打開 HSDB 的 Tools->Class Browser 功能,就能看到類 Animal 在 JVM 內部所對應的 instanceKlass 對象實例的內存地址,如圖所示 。

從jvm虛擬機角度看Java多態 ->(重寫override)的實現原理

從jvm虛擬機角度看Java多態 ->(重寫override)的實現原理

從jvm虛擬機角度看Java多態 ->(重寫override)的實現原理

由上圖可知,類 Animal 在JVM內部所對應的 instanceKlass的內存首地址是 0x00000000320004a8 ,上一節分析知道vtable 被分配在 instanceKlass的末尾位置,因此 vtable 的內存首地址是 :

0x00000000320004a8 + Oxlb8 = Ox0000000032000660

這裡的Oxlb8 是 instanceKlass 對象實例所佔的內存空間大小 。 得到 vtable 內存地址後,便可以使用 HSDB 的 mem 工具來查看這個地址處的內存數據。單擊 HSDB 工具欄上的 Windows->Console 按鈕,打開 HSDB 的終端控制檯,按回車鍵,然後輸入“ mem Ox32000660 6”命令,就可以查看從 vtable 內存首地址開始的連續 6 個雙字內容,如下所示:

從jvm虛擬機角度看Java多態 ->(重寫override)的實現原理

從jvm虛擬機角度看Java多態 ->(重寫override)的實現原理

在 64 位平臺上, 一個指針佔 8 字節 ,而 vtable 裡的每一個成員元素都是一個指針,因此這裡 mem 所輸出 的 6 行 ,正好是類 Animal 的 vtable 裡的 6 個方法指針,每一個指針指向 l 個方法在內存中的位置。 類 A 的 vtable 總個數是 6 ,其中前面 5 個是基類 java.lang.Object 中的 5 個方法的指針 。上面 mem 命令所輸出的第 6 行的指針, 一定就是指向類 Animal 自己的say方法的內存地址 。 使用HSDB 查看類 A 的方法的內存地址,如圖中所示,地址剛好對應得上。其它的類也可以用同樣的方式分析。

0x04 總結

前面對jvm 的vtable 進行了研究和驗證,再總結下特點:

1. vtable 分配在 instanceKlass對象實例的內存末尾 。

2. 其實vtable可以看作是一個數組,數組中的每一項成員元素都是一個指針,指針指向 Java 方法在 JVM 內部所對應的 method 實例對象的內存首地址 。

3. vtable是 Java 實現面向對象的多態性的機制,如果一個 Java 方法可以被繼承和重寫, 則最終通過 put_method_at函數將方法地址替換,完成 Java 方法的動態綁定。

4.Java 子類會繼承父類的 vtable,Java 中所有類都繼承自 java.lang.Object, java .lang.Object 中有 5 個虛方法(可被繼承和重寫):

void finalize()

boolean equals(Object)

String toString()

int hashCode()

Object clone()

因此,如果一個 Java 類中不聲明任何方法,則其 vtalbe 的長度默認為 5 。

5.Java類中不是每一個 Java 方法的內存地址都會保存到 vtable 表中,只有當 Java子類中聲明的 Java 方法是 public 或者 protected 的,且沒有 final 、 static 修飾,並且 Java 子類中的方法並非對父類方法的重寫時, JVM 才會在 vtable 表中為該方法增加一個引用 。

6.如果 Java 子類某個方法重寫了父類方法,則子類的vtable 中原本對父類方法的指針會被替換成子類對應的方法指針,調用put_method_at函數替換vtable中對應的方法指針。

以上只是個人學習的一點總結,水平能力有限,如果有不對的地方還請多多指教,萬分感謝。

更多幹貨,請關注看雪學院公眾號:ikanxue


分享到:


相關文章: