工具与环境:
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文件。

运行后如下图:

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 = ;
}
};
int main(){
Cpuls cplus;
return ;
}

这个 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 = ; // start off with super's vtable length
InstanceKlass* sk = (InstanceKlass*)super;
//获取父类 vtable 的大小,并将当前类的 vtable 的大小设置为父类 vtable 的大小。
vtable_length = super == NULL ? : 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 = ; 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();
// 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 == ) {
// 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() == , "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 ;
} 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 = ; 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 = ; 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 = ; 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, -, 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 > ) {
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 = ; 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 >= ) {
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 ( == 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 ( == strcmp(class_name, "Dog") || == strcmp(class_name, "Animal") || == strcmp(class_name, "Cat"))
{
printf("update_inherited_vtable %s\n",class_name); }
for(int i = ; 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
table() 0x14890520 vtableEntry *
table()[index] {_method=0x14890250 } vtableEntry Dog
say 0x148906e0 index
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中对应的方法指针。

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

欢迎关注公众号:

从虚拟机角度看Java多态->(重写override)的实现原理的更多相关文章

  1. 从JVM角度看Java多态

    首先,明确一下,Java多态的三个必要条件: 1. 继承 2. 子类重写父类方法 3. 父类引用指向子类对象 然后看一个例子 package test.xing; class Father{ prot ...

  2. 从JVM的角度看JAVA代码--代码优化

    从JVM的角度看JAVA代码–代码优化 从JVM的角度看JAVA代码代码优化 片段一反复计算 片段二反复比較 在JVM载入优化为class文件,运行class文件时,会有JIT(Just-In-Tim ...

  3. Java面向对象---重写(Override)与重载(Overload)

    一.重写(Override) 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变.即外壳不变,核心重写! 重写的好处在于子类可以根据需要,定义特定于自己的行为. 也就是说 ...

  4. 从设计模式的角度看Java程序优化

    一.前言 Java程序优化有很多种渠道,比如jvm优化.数据库优化等等,但都是亡羊补牢的措施,如果能在设计程序架构时利用设计模式就把程序的短板解决,就能使程序更加健壮切容易维护迭代 二.常用的设计模式 ...

  5. 从字节码的角度看Java内部类与外部类的互相访问

    Java中non-static内部类为何可以访问外部类的变量?Java中外部类又为何可以访问内部类的private变量?这两个问题困扰过我一段时间,查了一些网上的答案,大多从“闭包”概念入手,理解起来 ...

  6. 从成本角度看Java微服务

    近年来,微服务因其良好的灵活性和伸缩性等特点备受追捧,很多公司开始采用微服务架构或将已有的单体系统改造成微服务.IBM也于近日开源了轻量级Java微服务应用服务器 Open Liberty .但是采用 ...

  7. 从JDK源码角度看java并发线程的中断

    线程的定义给我们提供了并发执行多个任务的方式,大多数情况下我们会让每个任务都自行执行结束,这样能保证事务的一致性,但是有时我们希望在任务执行中取消任务,使线程停止.在java中要让线程安全.快速.可靠 ...

  8. 从JDK源码角度看java并发的公平性

    JAVA为简化开发者开发提供了很多并发的工具,包括各种同步器,有了JDK我们只要学会简单使用类API即可.但这并不意味着不需要探索其具体的实现机制,本文从JDK源码角度简单讲讲并发时线程竞争的公平性. ...

  9. 从JDK源码角度看java并发的原子性如何保证

    JDK源码中,在研究AQS框架时,会发现很多地方都使用了CAS操作,在并发实现中CAS操作必须具备原子性,而且是硬件级别的原子性,java被隔离在硬件之上,明显力不从心,这时为了能直接操作操作系统层面 ...

随机推荐

  1. update 多表链接

    update Aset A.shl = A.shl - B.shl from retmxls A,daysphz Bwhere A.spid = B.spid and A.fdbs = B.fdbs ...

  2. winform工具1-图片去除水印

    效果图: 思路: 1.获取图片 2.处理水印 3.保存处理的图片 代码: 获取图片: private void button1_Click(object sender, EventArgs e) { ...

  3. fork 炸弹

    一段古老的Linux shell程序 :() { :|:& };: 这是bash shell的写法,首先定义了一个函数:(),花括号里面是函数体,这里递归执行函数本身,通过管道在后台再递归运行 ...

  4. Android 单选按钮(RadioButton)和复选框(CheckBox)的使用

    1.RadioButton (1)介绍 (2)单选按钮点击事件的用法 (3)RadioButton与RadioGroup配合使用实现单选题功能 (4)xml布局及使用 <?xml version ...

  5. bzoj3224 普通平衡树 splay模板

    题目传送门 题目大意:完成一颗splay树. 思路:模板题,学着还是很有意思的. 学习splay树:蒟蒻yyb 该题模板:汪立超 #include<bits/stdc++.h> #defi ...

  6. HDU - 2256 矩阵快速幂 带根号的递推

    题意:求$ [(\sqrt{2}+\sqrt{3})^{2n}] mod 1024 $ 分析: 把指数的2带入 原式等于 $ [(5+2\sqrt{6})^n] $ 有一个重要的结论是n次运算后其结果 ...

  7. 【研究】CVE-2015-1635-HTTP.SYS远程执行代码漏洞(ms15-034)

    1.1.1  漏洞描述 在2015年4月安全补丁日,微软发布的众多安全更新中,修复了HTTP.sys中一处允许远程执行代码漏洞,编号为:CVE-2015-1635(MS15-034 ).利用HTTP. ...

  8. 【研究】curl测试不安全的HTTP请求

    漏洞名称: 启用了不安全的HTTP方法 安全风险:       可能会在Web 服务器上上载.修改或删除Web 页面.脚本和文件. 可能原因:       Web 服务器或应用程序服务器是以不安全的方 ...

  9. SQL Server Reporting Service(SSRS) 第七篇 常见错误汇总

    1. The current action cannot be completed. The user data source credentials do not meet the requirem ...

  10. apache ftpserver外网访问配置

    apache ftpserver搭建ftp服务非常简单,若只是内网访问,几乎不需要配置,直接启动即可.但若需要外网访问,则需要注意以下几点. 1.若是外网访问,主动模式是不行的,因为客户端报告给服务器 ...