HotSpot JVM源码分析 - 方法调用与解释执行(4):由invoke指令看字节码指令如何解释执行

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

本文以 invokeinterface 指令为例,分析 HotSpot JVM 如何解释执行 字节码指令

正文

前面文章展示了从操作数解析出 符号引用 再解析出 直接引用 的过程,解析出的信息存储在 常量池缓存 中。那么这些信息是怎么使用的呢?

直接上代码,因为代码较长,我们分段解释。

源码文件(openjdk\hotspot\src\share\vm\interpreter\bytecodeInterpreter.cpp)。

获取常量池缓存条目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CASE(_invokeinterface): {
u2 index = Bytes::get_native_u2(pc+1);

// QQQ Need to make this as inlined as possible. Probably need to split all the bytecode cases
// out so c++ compiler has a chance for constant prop to fold everything possible away.

ConstantPoolCacheEntry* cache = cp->entry_at(index);
if (!cache->is_resolved((Bytecodes::Code)opcode)) {
CALL_VM(InterpreterRuntime::resolve_from_cache(THREAD, (Bytecodes::Code)opcode),
handle_exception);
cache = cp->entry_at(index);
}

istate->set_msg(call_method);

字节码指令 后面,紧跟两个字节的操作数。如果已经解析过,操作数即为 常量池缓存条目 索引。

代码中,首先取出操作数,然后尝试籍此获得 常量池缓存条目

根据条目信息,可以知道是否已经解析过。如果没有解析过,就进行前面文章展示的解析流程。

解析完成,再获取 常量池缓存条目,此时一定存在该条目。

若强制virtual调用

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
    // Special case of invokeinterface called for virtual method of
// java.lang.Object. See cpCacheOop.cpp for details.
// This code isn't produced by javac, but could be produced by
// another compliant java compiler.
if (cache->is_forced_virtual()) {
Method* callee;
CHECK_NULL(STACK_OBJECT(-(cache->parameter_size())));
if (cache->is_vfinal()) {
callee = cache->f2_as_vfinal_method();
// Profile 'special case of invokeinterface' final call.
BI_PROFILE_UPDATE_FINALCALL();
} else {
// Get receiver.
int parms = cache->parameter_size();
// Same comments as invokevirtual apply here.
oop rcvr = STACK_OBJECT(-parms);
VERIFY_OOP(rcvr);
Klass* rcvrKlass = rcvr->klass();
callee = (Method*) rcvrKlass->method_at_vtable(cache->f2_as_index());
// Profile 'special case of invokeinterface' virtual call.
BI_PROFILE_UPDATE_VIRTUALCALL(rcvrKlass);
}
istate->set_callee(callee);
istate->set_callee_entry_point(callee->from_interpreted_entry());
#ifdef VM_JVMTI
if (JvmtiExport::can_post_interpreter_events() && THREAD->is_interp_only_mode()) {
istate->set_callee_entry_point(callee->interpreter_entry());
}
#endif /* VM_JVMTI */
istate->set_bcp_advance(5);
UPDATE_PC_AND_RETURN(0); // I'll be back...
}

有些情况下,虽然 字节码指令invokeinterface,但会强制走 invokevirtual

如果指令执行的方法是 final 的, 那么 常量池缓存条目_f2 字段存储的就是方法的 直接引用

否则,先从栈里获取到调用方法的对象实例 rcvr,再籍此获取到位于 元空间类元数据 rcvrKlass。此时,常量池缓存条目_f2 字段存储的是 虚表索引,有了索引,就可以从 类元数据 中的 虚表 中获取到方法的 直接引用

调用即可。

正常interface调用

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
    // this could definitely be cleaned up QQQ
Method* callee;
Klass* iclass = cache->f1_as_klass();
// InstanceKlass* interface = (InstanceKlass*) iclass;
// get receiver
int parms = cache->parameter_size();
oop rcvr = STACK_OBJECT(-parms);
CHECK_NULL(rcvr);
InstanceKlass* int2 = (InstanceKlass*) rcvr->klass();
itableOffsetEntry* ki = (itableOffsetEntry*) int2->start_of_itable();
int i;
for ( i = 0 ; i < int2->itable_length() ; i++, ki++ ) {
if (ki->interface_klass() == iclass) break;
}
// If the interface isn't found, this class doesn't implement this
// interface. The link resolver checks this but only for the first
// time this interface is called.
if (i == int2->itable_length()) {
VM_JAVA_ERROR(vmSymbols::java_lang_IncompatibleClassChangeError(), "", note_no_trap);
}
int mindex = cache->f2_as_index();
itableMethodEntry* im = ki->first_method_entry(rcvr->klass());
callee = im[mindex].method();
if (callee == NULL) {
VM_JAVA_ERROR(vmSymbols::java_lang_AbstractMethodError(), "", note_no_trap);
}

// Profile virtual call.
BI_PROFILE_UPDATE_VIRTUALCALL(rcvr->klass());

istate->set_callee(callee);
istate->set_callee_entry_point(callee->from_interpreted_entry());
#ifdef VM_JVMTI
if (JvmtiExport::can_post_interpreter_events() && THREAD->is_interp_only_mode()) {
istate->set_callee_entry_point(callee->interpreter_entry());
}
#endif /* VM_JVMTI */
istate->set_bcp_advance(5);
UPDATE_PC_AND_RETURN(0); // I'll be back...
}

常量池缓存条目_f1 字段存储的是 接口元数据_f2 字段存储是 接口表索引

从栈里获取到调用方法的对象实例 rcvr,再籍此获取到位于 元空间类元数据 rcvr->klass()

因为一个类只会继承自一个基类,却可以实现多个接口,所以 接口表 结构比 虚表 复杂。大体是,分别有 1接口偏移表N接口表接口偏移表 的条目存储了 接口元数据 和对应 接口表 的偏移地址。通过跟前面说的 _f1 字段存储的值进行比对,就能找到方法所在的那个接口,获取到其 接口表 的偏移地址。

有了 类元数据接口表 的偏移地址,就能找到对应的 接口表。再根据 _f2 字段存储的 接口表索引,就能找到方法的 直接引用

调用即可。

这样,该 字节码指令 解释执行即对应的方法调用就完成了。

话外

invokeinterfaceinvokevirtual 更复杂些,所以本文以此为例。

为了便于理解,选择了 CPP解释器 的执行过程,模板解释器 过程是类似的,只是生成了机器码供运行时执行。

结合前面两篇文章,这就是 invoke 类型的 字节码指令 解析引用并解释执行的主要过程。