HotSpot JVM源码分析 - 方法调用与解释执行(3):由invoke指令看如何将符号引用解析成直接引用

本系列文章基于 OpenJDK 9,聚焦 x86 平台。

本文以 invokevirtual 指令为例,分析 HotSpot JVM 解释器如何从 符号引用 解析出 直接引用 信息。

正文

文接上回。

继续看下面这段源码(openjdk\hotspot\src\share\vm\interpreter\linkResolver.cpp):

1
2
3
4
5
6
7
8
void LinkResolver::resolve_invokevirtual(CallInfo& result, Handle recv,
const constantPoolHandle& pool, int index,
TRAPS) {

LinkInfo link_info(pool, index, CHECK);
KlassHandle recvrKlass (THREAD, recv.is_null() ? (Klass*)NULL : recv->klass());
resolve_virtual_call(result, recv, recvrKlass, link_info, /*check_null_or_abstract*/true, CHECK);
}

上篇文章讲解了第 1 行代码,用以从操作数解析出 符号引用 信息。

本文关注第 3 行代码:

1
resolve_virtual_call(result, recv, recvrKlass, link_info, /*check_null_or_abstract*/true, CHECK);

即用以将 符号引用 解析成 直接引用

其实现如下:

1
2
3
4
5
6
7
8
9
void LinkResolver::resolve_virtual_call(CallInfo& result, Handle recv, KlassHandle receiver_klass,
const LinkInfo& link_info,
bool check_null_and_abstract, TRAPS) {
methodHandle resolved_method = linktime_resolve_virtual_method(link_info, CHECK);
runtime_resolve_virtual_method(result, resolved_method,
link_info.resolved_klass(),
recv, receiver_klass,
check_null_and_abstract, CHECK);
}

可以看到,依次调用了链接时和运行时的解析方法。

链接时解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
methodHandle LinkResolver::linktime_resolve_virtual_method(const LinkInfo& link_info,
TRAPS) {
// normal method resolution
methodHandle resolved_method = resolve_method(link_info, Bytecodes::_invokevirtual, CHECK_NULL);

assert(resolved_method->name() != vmSymbols::object_initializer_name(), "should have been checked in verifier");
assert(resolved_method->name() != vmSymbols::class_initializer_name (), "should have been checked in verifier");

// check if private interface method
KlassHandle resolved_klass = link_info.resolved_klass();
KlassHandle current_klass = link_info.current_klass();

// This is impossible, if resolve_klass is an interface, we've thrown icce in resolve_method
...

// check if not static
...

return resolved_method;
}

先调用 resolve_method 方法解析出 方法句柄,后面又做了一系列合法性检查。

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
methodHandle LinkResolver::resolve_method(const LinkInfo& link_info,
Bytecodes::Code code, TRAPS) {

Handle nested_exception;
KlassHandle resolved_klass = link_info.resolved_klass();

// 1. For invokevirtual, cannot call an interface method
...

// 2. check constant pool tag for called method - must be JVM_CONSTANT_Methodref
...

// 3. lookup method in resolved klass and its super klasses
methodHandle resolved_method = lookup_method_in_klasses(link_info, true, false, CHECK_NULL);

// 4. lookup method in all the interfaces implemented by the resolved klass
if (resolved_method.is_null() && !resolved_klass->is_array_klass()) { // not found in the class hierarchy
resolved_method = lookup_method_in_interfaces(link_info, CHECK_NULL);

if (resolved_method.is_null()) {
// JSR 292: see if this is an implicitly generated method MethodHandle.linkToVirtual(*...), etc
resolved_method = lookup_polymorphic_method(link_info, (Handle*)NULL, (Handle*)NULL, THREAD);
if (HAS_PENDING_EXCEPTION) {
nested_exception = Handle(THREAD, PENDING_EXCEPTION);
CLEAR_PENDING_EXCEPTION;
}
}
}

// 5. method lookup failed
...

// 6. access checks, access checking may be turned off when calling from within the VM.
...

return resolved_method;
}

分了很多步骤,重点是 34 步,其他还是都是一些合法性检查。

这两步原理上类似,我们关注步骤 3

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
methodHandle LinkResolver::lookup_method_in_klasses(const LinkInfo& link_info,
bool checkpolymorphism,
bool in_imethod_resolve, TRAPS) {
KlassHandle klass = link_info.resolved_klass();
Symbol* name = link_info.name();
Symbol* signature = link_info.signature();

// Ignore overpasses so statics can be found during resolution
Method* result = klass->uncached_lookup_method(name, signature, Klass::skip_overpass);

if (klass->is_array_klass()) {
// Only consider klass and super klass for arrays
return methodHandle(THREAD, result);
}

InstanceKlass* ik = InstanceKlass::cast(klass());

...

// Before considering default methods, check for an overpass in the
// current class if a method has not been found.
if (result == NULL) {
result = ik->find_method(name, signature);
}

if (result == NULL) {
Array<Method*>* default_methods = ik->default_methods();
if (default_methods != NULL) {
result = InstanceKlass::find_method(default_methods, name, signature);
}
}

...
return methodHandle(THREAD, result);
}

主要就是 uncached_lookup_method 方法(F:\External\openjdk-9_src\openjdk\hotspot\src\share\vm\oops\instanceKlass.cpp)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Method* InstanceKlass::uncached_lookup_method(const Symbol* name,
const Symbol* signature,
OverpassLookupMode overpass_mode) const {
OverpassLookupMode overpass_local_mode = overpass_mode;
const Klass* klass = this;
while (klass != NULL) {
Method* const method = InstanceKlass::cast(klass)->find_method_impl(name,
signature,
overpass_local_mode,
find_static,
find_private);
if (method != NULL) {
return method;
}
klass = klass->super();
overpass_local_mode = skip_overpass; // Always ignore overpass methods in superclasses
}
return NULL;
}

InstanceKlass 是类的元数据,存在于 方法区,包含类的常量池、字段列表、方法列表等。

查找方式是先在当前类查找,找不到就去基类查找,直到找到或到达顶层。

这些 find_method 殊途同归,会调用到 InstanceKlass 的类方法 find_method_index

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
int InstanceKlass::find_method_index(const Array<Method*>* methods,
const Symbol* name,
const Symbol* signature,
OverpassLookupMode overpass_mode,
StaticLookupMode static_mode,
PrivateLookupMode private_mode) {
const bool skipping_overpass = (overpass_mode == skip_overpass);
const bool skipping_static = (static_mode == skip_static);
const bool skipping_private = (private_mode == skip_private);
const int hit = binary_search(methods, name);
if (hit != -1) {
const Method* const m = methods->at(hit);

// Do linear search to find matching signature. First, quick check
// for common case, ignoring overpasses if requested.
if (method_matches(m, signature, skipping_overpass, skipping_static, skipping_private)) {
return hit;
}

// search downwards through overloaded methods
int i;
for (i = hit - 1; i >= 0; --i) {
const Method* const m = methods->at(i);
assert(m->is_method(), "must be method");
if (m->name() != name) {
break;
}
if (method_matches(m, signature, skipping_overpass, skipping_static, skipping_private)) {
return i;
}
}
// search upwards
for (i = hit + 1; i < methods->length(); ++i) {
const Method* const m = methods->at(i);
assert(m->is_method(), "must be method");
if (m->name() != name) {
break;
}
if (method_matches(m, signature, skipping_overpass, skipping_static, skipping_private)) {
return i;
}
}
// not found
#ifdef ASSERT
const int index = (skipping_overpass || skipping_static || skipping_private) ? -1 :
linear_search(methods, name, signature);
assert(-1 == index, "binary search should have found entry %d", index);
#endif
}
return -1;
}

就是遍历方法列表,进行条件匹配。

经过上述过程,就解析到了方法的 直接引用

那么,上面看到运行时解析又是做什么的呢?

运行时解析

简化后的代码如下:

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
void LinkResolver::runtime_resolve_virtual_method(CallInfo& result,
const methodHandle& resolved_method,
KlassHandle resolved_klass,
Handle recv,
KlassHandle recv_klass,
bool check_null_and_abstract,
TRAPS) {

// setup default return values
int vtable_index = Method::invalid_vtable_index;
methodHandle selected_method;

...

// do lookup based on receiver klass using the vtable index
if (resolved_method->method_holder()->is_interface()) { // default or miranda method
vtable_index = vtable_index_of_interface_method(resolved_klass,
resolved_method);
assert(vtable_index >= 0 , "we should have valid vtable index at this point");

selected_method = methodHandle(THREAD, recv_klass->method_at_vtable(vtable_index));
} else {
// at this point we are sure that resolved_method is virtual and not
// a default or miranda method; therefore, it must have a valid vtable index.
assert(!resolved_method->has_itable_index(), "");
vtable_index = resolved_method->vtable_index();
// We could get a negative vtable_index for final methods,
// because as an optimization they are they are never put in the vtable,
// unless they override an existing method.
// If we do get a negative, it means the resolved method is the the selected
// method, and it can never be changed by an override.
if (vtable_index == Method::nonvirtual_vtable_index) {
assert(resolved_method->can_be_statically_bound(), "cannot override this method");
selected_method = resolved_method;
} else {
selected_method = methodHandle(THREAD, recv_klass->method_at_vtable(vtable_index));
}
}

...

// setup result
result.set_virtual(resolved_klass, recv_klass, resolved_method, selected_method, vtable_index, CHECK);
}

过程很简单,首先获取方法的 虚表索引vtable_index),再通过该索引获取方法真正的 直接引用

这里用到了 “真正的” 这个词,什么意思?

假设有如下代码:

1
2
3
4
5
6
7
8
9
10
class B {
public void hello() {}
}

class D extends B {
public void hello() {}
}

B b = new D();
b.hello();

这里,b静态类型B实际类型D

BD 都有各自的 虚表,存放着 虚方法 的地址,即 直接引用

上面 链接时 解析出的 直接引用Bhello 方法地址,D 重写overwrite)了该方法,故而真正的 直接引用 应该是 Dhello 方法地址,这个地址就是 运行时 解析出来的。

通过 链接时 解析出的 直接引用,可以获取到基类方法对应的 虚表索引,而子类重写后,同一方法的 虚表索引 保持不变,只是子类的 虚表 的该项内容重写为子类的方法地址。

示例中的 D 即源码中的 recv_kclass,根据 虚表索引 查找 实际类型 D虚表,获取到的就是 D 中的方法地址,也即真正的的 直接引用

话外

这就是 动态分派,因为这种分派只看 实际类型 一个因素,所以 重写 属于 动态单分派