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

工具與環境:
Windows 7 x64企業版
Cygwin x64
jdk1.8.0_162
openjdk-8u40-src-b25-10_feb_2015
Vs2010 professionaljava

0x00: Java多態簡單介紹bootstrap

1.多態的概念windows

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

2.多態的優勢併發

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

b.可擴充性: 多態對代碼具備可擴充性。
c.靈活性: 它在應用中體現了靈活多樣的操做,提升了使用效率。
d.簡化性: 多態簡化對應用軟件的代碼編寫和修改過程,尤爲在處理大量對象的運算和操做時,這個特色尤其突出和重要。less

3.示例代碼(如下分析基於該代碼):jvm

 1 public class Animal{
 2     public void say(){
 3         System.out.println("is animal");
 4     }
 5     public static void main(String[] args){
 6         Animal animal = new Dog();
 7         run(animal);
 8         animal = new Cat();
 9         run(animal);
10     }
11     public static void run(Animal animal){
12         animal.say();
13     }
14 }
15 class Dog extends Animal{
16     public void say(){
17         System.out.println("is Dog");
18     }
19 }
20 class Cat extends Animal{
21     public void say(){
22         System.out.println("is Cat");
23     }
24 }

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

運行後以下圖:函數

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. 示例代碼:

 1 class Cpuls{
 2 public: 
 3     int x;
 4 public:
 5      void func(){
 6         this->x = 2;
 7     }
 8 };
 9 int main(){
10     Cpuls cplus;
11     return 0;
12 }

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

因爲 CPLUS 類中僅包含 l 個 int 類型的變量 ,所以觀察結果中的 cplus 實例內存地址,只有變量 x 。
如今將 C++類中的 run方法修改一下,變成虛方法,在觀察對象的內存表現:

注意看,如今的值變了,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中函數重寫機制。

  1. 當咱們經過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<Method*>* all_mirandas, Klass* super,
    Array<Method*>* methods, AccessFlags class_flags,
    Handle classloader, Symbol* classname, Array<Klass*>* 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<Method*> 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;//返回虛方法個數
}

上面這段代碼計算 vtable 個數的思路主要分爲兩步 :
a:獲取父類 vtable 的個數,並將當前類的 vtable 的個數設置爲父類 vtable 的個數。
b:循環遍歷當前 Java 類的每個方法 ,調用 needs_new_vtable_entry()函數進行判斷,若是判斷的結果是 true ,則將 vtable 的個數增 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 方法是構造
 函數<init>,則返回 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())
      // <init> 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時會用到。

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

      /*判斷是否重寫或有虛函數,若是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<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_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<int>* 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 <init> 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() : "<NULL>";
    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函數地址

當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 對象實例的內存地址,如圖所示 。

由上圖可知,類 Animal 在JVM內部所對應的 instanceKlass的內存首地址是 0x00000000320004a8 ,上一節分析知道vtable 被分配在 instanceKlass的末尾位置,所以 vtable 的內存首地址是 :
0x00000000320004a8 + Oxlb8 = Ox0000000032000660
這裏的 Oxlb8 是 instanceKlass 對象實例所佔的內存空間大小 。 獲得 vtable 內存地址後,即可以使用 HSDB 的 mem 工具來查看這個地址處的內存數據。單擊 HSDB 工具欄上的 Windows->Console 按鈕,打開 HSDB 的終端控制檯,按回車鍵,而後輸入「 mem Ox32000660 6」命令,就能夠查看從 vtable 內存首地址開始的連續 6 個雙字內容,以下所示:

在 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中對應的方法指針。

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

 

歡迎關注公衆號:

相關文章
相關標籤/搜索