java方法调用的实现

Posted by My Blog on November 30, 2023

0.why?

1.

  • invokevirtual
  • invokestatic
  • invokespecial
  • invokeinterface
  • Invokedynamic

是如何实现java类的多态和继承特性的。

2.什么是method table?

3.什么是stackframe?

1.既生瑜何生亮?

1.1 invokevirtual

invokevirtual比较好理解,它根据运行时对象的真实类型,来决定应该调用的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Dog {
    public void bark() {
        System.out.println("dog bark!");
    }
}

public class CockerSpaniel extends Dog {
    public static void main(String args[]) {
        Random r = new Random();
        Dog dog;
        if (r.nextInt() % 2 == 0){
            dog = new Dog();
        } else {
            dog = new CockerSpaniel();
        }
        dog.bark();
    }

    public void bark() {
        System.out.println("CockerSpaniel bark!");
    }
}

编译后字节码:

main方法字节码 dog.bark调用类型
bytecode methodref

可见,尽管dog.bark方法字节码所在类是Dog,但是实际调用时,还是会根据对象实际类型来动态决定,而不是根据静态编译类型。这一点,肯定取决于jvm内部对invokevirtual的实现,后面再说。

1.2 为什么需要invokespecial?

  • 对象初始化init方法

  • private 方法
  • super.xxx() 形式调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Baseclass {
    protected void f1() {
        System.out.println("base f1");
    }
}

public class Superclass extends Baseclass{
    private void interestingMethod() {
        System.out.println("Superclass's interesting method.");
    }

    void exampleMethod() {
        interestingMethod();
    }

    protected void f1() {
        System.out.println("super f1");
    }
}

class Subclass extends Superclass {
    private void interestingMethod() {
        System.out.println("Subclass's interesting method.");
    }

    protected void f1() {
        super.f1();
        System.out.println("sub f1");
    }

    public static void main(String args[]) {
        Subclass me = new Subclass();
        me.exampleMethod();
        me.f1();
    }
}

编译结果

Subclass::f1 f1
Subclass::init init
Superclass::exampleMethod exampleMethod

上述三种场景下,如果仍然使用invokevirtual显然是错误的,此时必须根据编译时静态类型来决定调用哪个方法。因此必须使用invokespecial来区分。

  • 如果注释掉Superclass中的f1方法,则Subclass中f1方法中调用super.f1()实际调用Baseclass.f1,即由近及远向上追溯
  • 尝试修改一下Subclass::main()me.f1()字节码,将其从invokevirtual->invokespecial(IntelliJ jclasslib支持修改字节码)
invokevirtual Superclass’s interesting method.
super f1
sub f1
invokespecial Superclass’s interesting method.
super f1

1.2 为什么需要invokeinterface?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class UnrelatedClass {
    public static void main(String args[]) {
        Subclass2 sc = new Subclass2(0); 
        sc.interfaceMethod(); // invokevirtual

        InYourFace iyf = sc;
        iyf.interfaceMethod();  // invokeinterface
    }
}

interface InYourFace {
    void interfaceMethod ();
}

class Subclass1 implements InYourFace {
    Subclass1(int i) {
        super(); 
    }

    public void interfaceMethod() {}
}

class Subclass2 extends Subclass1 {
    Subclass2(int i) {
        super(i);
    }
}

编译后字节码:

| invokeinterface | | ———————————————————— |

The invokeinterface opcode performs the same function as invokevirtual, it invokes instance methods and uses dynamic binding. The difference between these two instructions is that invokevirtual is used when the type of the reference is a class, whereas invokeinterface is used when the type of the reference is an interface.

The Java Virtual Machine uses a different opcode to invoke a method on an interface reference because it can’t make as many assumptions about the method table offset given an interface reference as it can given a class reference. (Method tables are described in Chapter 8, “The Linking Model.”) Given a class reference, a method will always occupy the same position in the method table, independent of the actual class of the object. This is not true given an interface reference. The method could occupy different locations for different classes that implement the same interface.1

  • invokeinterface 和 invokevirtual大体相同,都实现多态。当且仅当使用interface类型调用方法时,使用invokeinterface
  • invokevirtual在方法表(method table)中的索引是确定的,而invokeinterface是不确定的

那么问题来了,什么是method table?

2.什么是method table

首先看一段代码2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
interface Friendly {
    void sayHello();
    void sayGoodbye();
}

class Dog {
    // How many times this dog wags its tail when saying hello.
    private int wagCount = ((int) (Math.random() * 5.0)) + 1;

    void sayHello() {
        System.out.print("Wag");
        for (int i = 0; i < wagCount; ++i) {
            System.out.print(", wag");
        }
        System.out.println(".");
    }

    public String toString() {
        return "Woof!";
    }
}

class CockerSpaniel extends Dog implements Friendly {
    // How many times this Cocker Spaniel woofs when saying hello.
    private int woofCount = ((int) (Math.random() * 4.0)) + 1;

    // How many times this Cocker Spaniel wimpers when saying goodbye.
    private int wimperCount = ((int) (Math.random() * 3.0)) + 1;

    public void sayHello() {
        // Wag that tail a few times.
        super.sayHello();

        System.out.print("Woof");
        for (int i = 0; i < woofCount; ++i) {
            System.out.print(", woof");
        }
        System.out.println("!");
    }

    public void sayGoodbye() {
        System.out.print("Wimper");
        for (int i = 0; i < wimperCount; ++i) {
            System.out.print(", wimper");
        }
        System.out.println(".");
    }
}

class Cat implements Friendly {
    public void eat() {
        System.out.println("Chomp, chomp, chomp.");
    }

    public void sayHello() {
        System.out.println("Rub, rub, rub.");
    }

    public void sayGoodbye() {
        System.out.println("Scamper.");
    }

    protected void finalize() {
        System.out.println("Meow!");
    }
}

class Example4 {
    public static void main(String[] args) {
        Dog dog = new CockerSpaniel();

        dog.sayHello();

        Friendly fr = (Friendly) dog;

        // Invoke sayGoodbye() on a CockerSpaniel object through a reference of type Friendly.
        fr.sayGoodbye();

        fr = new Cat();

        // Invoke sayGoodbye() on a Cat object through a reference of type Friendly.
        fr.sayGoodbye();
    }
}

对象在jvm中的结构:

object layout 对象在堆上的结构(假定按定义顺序,且基类先于子类)
dog method table Dog对象method table
CockerSpaniel layout CockerSpaniel对象method table
cat layout Cat对象method table
  • 具有类继承关系对象方法,其在method table内的索引保持不变
  • 接口的方法实现,由于其不一定基于同一个基类,同一个方法索引可能不同,因此需要使用invokeinterface来区分

3.method table 存在于何处,长什么样子3

| object layout | | ———————————————————— |

上面Data作为一个java class,完成加载后,在jvm内部由一个instanceKlass对象表示,那么这个对象其在内存中的结构即如上图第二行所示。可以看到VTable紧接内存对象头之后,那么它到底是什么时候分配的,长度几何呢?我们来从 jdk8u源码一探究竟。

a. hotspot/src/share/vm/oops/instanceKlass.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
static InstanceKlass* allocate_instance_klass(
                                          ClassLoaderData* loader_data,
                                          int vtable_len,
                                          int itable_len,
                                          int static_field_size,
                                          int nonstatic_oop_map_size,
                                          ReferenceType rt,
                                          AccessFlags access_flags,
                                          Symbol* name,
                                          Klass* super_klass,
                                          bool is_anonymous,
                                          TRAPS);
											<- ClassFileParser::parseClassFile

该方法分配一个InstanceKlass实例,由ClassFileParser::parseClassFile在类加载中完成.class文件解析后调用,以生成java类定义在jvm中的数据结构表达。上面有个关键参数vtable_len,即为java类中需要多态调用关系方法(一般为public/protected 型实例方法)的数量(实际是存储方法指针数组长度)。同理itable_len表示接口方法数量,暂且不表。

b. vtable_len是怎么来的呢?

hotspot/src/share/vm/oops/klassVtable.cpp

klassVtable::compute_vtable_size_and_num_mirandasparseClassFile中完成,在分配allocate_instance_klass之前。内部逻辑不赘述,其长度为父类vtable_len和本类相关方法长度之和。

c. allocate_instance_klass实现

1
2
3
4
5
6
7
8
9
10
int size = InstanceKlass::size(vtable_len, itable_len, nonstatic_oop_map_size,
                                 access_flags.is_interface(), is_anonymous);
  // Allocation
	// normal class
      ik = new (loader_data, size, THREAD) InstanceKlass(
        vtable_len, itable_len, static_field_size, nonstatic_oop_map_size, rt,
        access_flags, is_anonymous);
  ......
  return ik;
}

首先可以看到size综合了各种长度,其次利用了c++中的placement new语义完成对象构造。这个new运算符重载在基类

hotspot/src/share/vm/oops/klass.hpp中。

d. vtable定义

1
2
3
4
// instanceKlass.hpp
static int vtable_start_offset()    { return header_size(); }
intptr_t* start_of_vtable() const        { return ((intptr_t*)this) + vtable_start_offset(); }
static int vtable_length_offset()   { return offset_of(InstanceKlass, _vtable_len) / HeapWordSize; }

e. 方法排序规则

vtable中方法并不是按照名称升序排列的,而是按照名称对象内存地址排序的:

1
2
3
4
5
6
7
8
9
10
ClassFileParser::parseClassFile
  -> sort_methods(methods)
  	-> Method::sort_methods(methods)
  		-> a->name()->fast_compare(b->name())
  			-> Symbol::fast_compare

int Symbol::fast_compare(Symbol* other) const {
 return (((uintptr_t)this < (uintptr_t)other) ? -1
   : ((uintptr_t)this == (uintptr_t) other) ? 0 : 1);
}

从上面的定义可以明确,vtable启始于对象header后。至此,vtable定义完成了,预留空间包含了父类和当前类方法,与前面的数组图示相符。接下来看是如何指向方法并且实现多态调用的。

4.多态调用的实现

先倒序罗列vtable初始化整体过程,再对其中重点方法加以说明。

hotspot/src/share/vm/oops/klassVtable.hpp,说明klassVtable可以理解为一个helper,vtable实际仍然存在于instanceKlass实例中。

1
2
3
4
5
6
7
8
9
klassVtable::initialize_from_super(super)
klassVtable::update_inherited_vtable
	<- klassVtable::initialize_vtable
  	<- instanceKlass::link_class_impl
  		<- instanceKlass::eager_initialize_impl
  			<- instanceKlass::eager_initialize
  				<- SystemDictionary::parse_stream
  					or 
  				<- SystemDictionary::define_instance_class

parse_stream或者define_instance_class很显然是类加载过程的关键方法,上述调用路径并未穷尽所有,只是其中一种。

首先,initialize_from_super将父类vtable内容完整拷贝到当前类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Copy super class's vtable to the first part (prefix) of this class's vtable
int klassVtable::initialize_from_super(KlassHandle super) {
  // copy methods from superKlass
    assert(super->oop_is_instance(), "must be instance klass");
    InstanceKlass* sk = (InstanceKlass*)super();
    klassVtable* superVtable = sk->vtable();
    superVtable->copy_vtable_to(table());
    ......
    return superVtable->length();
}

void klassVtable::copy_vtable_to(vtableEntry* start) {
  Copy::disjoint_words((HeapWord*)table(), (HeapWord*)start, _length * vtableEntry::size());
}

其次,update_inherited_vtable根据方法名和签名决定是否覆盖父类方法,否则新增放入vtable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Update child's copy of super vtable for overrides
// OR return true if a new vtable entry is required.
// Only called for InstanceKlass's, i.e. not for arrays
// If that changed, could not use _klass as handle for klass
bool klassVtable::update_inherited_vtable(InstanceKlass* klass, methodHandle target_method,
                                          int super_vtable_len, int default_index,
                                          bool checkconstraints, TRAPS) {
  bool allocate_new = true;

  Symbol* name = target_method()->name();
  Symbol* signature = target_method()->signature();
  
  Symbol* target_classname = target_klass->name();
  for(int i = 0; i < super_vtable_len; i++) {
    Method* super_method;
    if (is_preinitialized_vtable()) {
      // If this is a shared class, the vtable is already in the final state (fully
      // initialized). Need to look at the super's vtable.
      klassVtable* superVtable = super->vtable();
      super_method = superVtable->method_at(i);
    } else {
      super_method = method_at(i);
    }
    // Check if method name matches
    if (super_method->name() == name && super_method->signature() == signature) {
       ......
       put_method_at(target_method(), i);
       if (!is_default) {
         target_method()->set_vtable_index(i);
       } 
        ......
      } 
    }
  }
  return allocate_new;
}

至此,也就完成了vtable的初始化。对象实例在调用某个方法时,根据头部类指针回溯到当前类instanceKlass,再从vtable索引找到方法实际指针,若为覆盖则调用基类方法,若已覆盖则调用当前类方法 ,从而实现多态。

5.接口方法调用

1.2小节的代码示例提到了invokeinterface:

1
2
3
4
5
Subclass2 sc = new Subclass2(0); 
sc.interfaceMethod(); // invokevirtual

InYourFace iyf = sc;
iyf.interfaceMethod();  // invokeinterface

为什么要有上述区分呢?

Direct references to instance variables and instance methods are offsets. A direct reference to an instance variable is likely the offset from the start of the object’s image to the location of the instance variable. A direct reference to an instance method is likely an offset into a method table.

Using offsets to represent direct references to instance variables and instance methods depends on a predictable ordering of the fields in a class’s object image and the methods in a class’s method table. Although implementation designers may choose any way of placing instance variables into an object image or methods into a method table, they will almost certainly use the same way for all types. Therefore, in any one implementation, the ordering of fields in an object and methods in a method table is defined and predictable.2

上面这段话的意思是当方法调用完成解析后(resolution),对于任何实例(instance)调用,方法的偏移量(offset)是确定的。而如果调用静态类型是接口时,其方法偏移量是不确定的,因而基于接口引用的方法调用一般而言要慢一些。

| class hierarchy | 依照左图类和接口的继承关系,类C和类E的method table中,Interface A的方法索引显然无法保持一致 | | ———————————————————— | ———————————————————— |

  • 静态类型决定字节码编译结果,同样一个方法,调用类型是class时invokevirtual,调用类型是interface时invokeinterface
  • 多态调用有两种:基于继承,基于接口

6.references