首页
论坛
专栏
课程
8

从jvm虚拟机角度看Java多态 ->(重写override)的实现原理

我是小三 2018-3-23 19:01 1723

工具与环境:

Windows 7 x64企业版

Cygwin x64

jdk1.8.0_162

openjdk-8u40-src-b25-10_feb_2015

Vs2010 professional

个人博客:http://www.cnblogs.com/2014asm/

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文件。

运行后如下图:


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 调试时看内存情况,没有虚函数时对象的内存表现如下图:


由于 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中函数重写机制。

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<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;//返回虚方法个数
}

上面这段代码计算 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 方法是构造
 函数<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中对应的方法指针。

以上只是个人学习的一点总结,水平能力有限,如果有不对的地方还请多多指教,万分感谢。









快讯:看雪智能设备漏洞挖掘公开课招生中!

最后于 2018-3-23 19:22 被我是小三编辑 ,原因:
上传的附件:
最新回复 (5)
KevinsBobo 2018-3-24 14:21
2
感谢分享,欢迎楼主来分享更多该系列的知识
点中你的心 2018-3-25 09:52
3
感谢分享
还可以这么深入
Jason姚 2018-3-25 13:55
4
感谢分享
聖blue 2018-3-25 23:22
5
hrfhrf 4天前
6
牛人一个
返回