第34篇-解析invokeinterface字节码指令
与invokevirtual指令类似,当没有对目标方法进行解析时,需要调用LinkResolver::resolve_invoke()函数进行解析,这个函数会调用其它一些函数完成方法的解析,如下图所示。
上图中粉色的部分与解析invokevirtual字节码指令有所区别,resolve_pool()函数及其调用的相关函数在介绍invokevirtual字节码指令时详细介绍过,这里不再介绍。
调用LinkResolver::resolve_invokeinterface()函数对字节码指令进行解析。函数的实现如下:
void LinkResolver::resolve_invokeinterface(
CallInfo& result,
Handle recv,
constantPoolHandle pool,
int index, // 指的是常量池缓存项的索引
TRAPS
) {
KlassHandle resolved_klass;
Symbol* method_name = NULL;
Symbol* method_signature = NULL;
KlassHandle current_klass;
// 解析常量池时,传入的参数pool(根据当前栈中要执行的方法找到对应的常量池)和
// index(常量池缓存项的缓存,还需要映射为原常量池索引)是有值的,根据这两个值能够
// 解析出resolved_klass和要查找的方法名称method_name和方法签名method_signature
resolve_pool(resolved_klass, method_name, method_signature, current_klass, pool, index, CHECK); KlassHandle recvrKlass (THREAD, recv.is_null() ? (Klass*)NULL : recv->klass());
resolve_interface_call(result, recv, recvrKlass, resolved_klass, method_name, method_signature, current_klass, true, true, CHECK);
}
我们接着看resolve_interface_call()函数的实现,如下:
void LinkResolver::resolve_interface_call(
CallInfo& result,
Handle recv,
KlassHandle recv_klass,
KlassHandle resolved_klass,
Symbol* method_name,
Symbol* method_signature,
KlassHandle current_klass,
bool check_access,
bool check_null_and_abstract,
TRAPS
) {
methodHandle resolved_method;
linktime_resolve_interface_method(resolved_method, resolved_klass, method_name, method_signature, current_klass, check_access, CHECK);
runtime_resolve_interface_method(result, resolved_method, resolved_klass, recv, recv_klass, check_null_and_abstract, CHECK);
}
调用2个函数对方法进行解析。首先看linktime_resolve_interface_method()函数的实现。
调用linktime_resolve_interface_method()函数会调用LinkResolver::resolve_interface_method()函数,此函数的实现如下:
void LinkResolver::resolve_interface_method(
methodHandle& resolved_method,
KlassHandle resolved_klass,
Symbol* method_name,
Symbol* method_signature,
KlassHandle current_klass,
bool check_access,
bool nostatics,
TRAPS
) {
// 从接口和父类java.lang.Object中查找方法,包括静态方法
lookup_method_in_klasses(resolved_method, resolved_klass, method_name, method_signature, false, true, CHECK); if (resolved_method.is_null()) {
// 从实现的所有接口中查找方法
lookup_method_in_interfaces(resolved_method, resolved_klass, method_name, method_signature, CHECK);
if (resolved_method.is_null()) {
// no method found
// ...
}
} // ...
}
首先调用LinkResolver::lookup_method_in_klasses()函数进行方法查找,在之前介绍过invokevirtual字节码指令时介绍过这个函数,不过只介绍了与invokevirtual指令相关的处理逻辑,这里需要继续查看invokeinterface的相关处理逻辑,实现如下:
void LinkResolver::lookup_method_in_klasses(
methodHandle& result,
KlassHandle klass,
Symbol* name,
Symbol* signature,
bool checkpolymorphism,
// 对于invokevirtual来说,值为false,对于invokeinterface来说,值为true
bool in_imethod_resolve,
TRAPS
) {
Method* result_oop = klass->uncached_lookup_method(name, signature); // 在接口中定义方法的解析过程中,忽略Object类中的静态和非public方法,如
// clone、finalize、registerNatives
if (
in_imethod_resolve &&
result_oop != NULL &&
klass->is_interface() &&
(result_oop->is_static() || !result_oop->is_public()) &&
result_oop->method_holder() == SystemDictionary::Object_klass() // 方法定义在Object类中
) {
result_oop = NULL;
} if (result_oop == NULL) {
Array<Method*>* default_methods = InstanceKlass::cast(klass())->default_methods();
if (default_methods != NULL) {
result_oop = InstanceKlass::find_method(default_methods, name, signature);
}
}
// ...
result = methodHandle(THREAD, result_oop);
}
调用uncached_lookup_method()函数从当前类和父类中查找,如果没有找到或找到的是Object类中的不合法方法,则会调用find_method()函数从默认方法中查找。在Java8的新特性中有一个新特性为接口默认方法,该新特性允许我们在接口中添加一个非抽象的方法实现,而这样做的方法只需要使用关键字default修饰该默认实现方法即可。
uncached_lookup_method()函数的实现如下:
Method* InstanceKlass::uncached_lookup_method(Symbol* name, Symbol* signature) const {
Klass* klass = const_cast<InstanceKlass*>(this);
bool dont_ignore_overpasses = true;
while (klass != NULL) {
Method* method = InstanceKlass::cast(klass)->find_method(name, signature);
if ((method != NULL) && (dont_ignore_overpasses || !method->is_overpass())) {
return method;
}
klass = InstanceKlass::cast(klass)->super();
dont_ignore_overpasses = false; // 不要搜索父类中的overpass方法
}
return NULL;
}
从当前类和父类中查找方法。当从类和父类中查找方法时,调用find_method()函数,最终调用另外一个重载函数find_method()从InstanceKlass::_methods属性中保存的方法中进行查找;当从默认方法中查找方法时,调用find_method()函数从InstanceKlass::_default_methods属性中保存的方法中查找。重载的find_method()函数的实现如下:
Method* InstanceKlass::find_method(Array<Method*>* methods, Symbol* name, Symbol* signature) {
int hit = find_method_index(methods, name, signature);
return hit >= 0 ? methods->at(hit): NULL;
}
其实调用find_method_index()函数就是根据二分查找来找名称为name,签名为signature的方法,因为InstanceKlass::_methods和InstanceKlass::_default_methods属性中的方法已经进行了排序,关于这些函数中存储的方法及如何进行排序在《深入剖析Java虚拟机:源码剖析与实例详解(基础卷)》一书中详细介绍过,这里不再介绍。
调用的LinkResolver::runtime_resolve_interface_method()函数的实现如下:
void LinkResolver::runtime_resolve_interface_method(
CallInfo& result,
methodHandle resolved_method,
KlassHandle resolved_klass,
Handle recv,
KlassHandle recv_klass,
bool check_null_and_abstract, // 对于invokeinterface来说,值为false
TRAPS
) {
// ... methodHandle sel_method; lookup_instance_method_in_klasses(
sel_method,
recv_klass,
resolved_method->name(),
resolved_method->signature(),
CHECK); if (sel_method.is_null() && !check_null_and_abstract) {
sel_method = resolved_method;
} // ...
// 如果查找接口的实现时找到的是Object类中的方法,那么要通过vtable进行分派,所以我们需要
// 更新的是vtable相关的信息
if (!resolved_method->has_itable_index()) {
int vtable_index = resolved_method->vtable_index();
assert(vtable_index == sel_method->vtable_index(), "sanity check");
result.set_virtual(resolved_klass, recv_klass, resolved_method, sel_method, vtable_index, CHECK);
} else {
int itable_index = resolved_method()->itable_index();
result.set_interface(resolved_klass, recv_klass, resolved_method, sel_method, itable_index, CHECK);
}
}
当没有itable索引时,通过vtable进行动态分派;否则通过itable进行动态分派。
调用的lookup_instance_method_in_klasses()函数的实现如下:
void LinkResolver::lookup_instance_method_in_klasses(
methodHandle& result,
KlassHandle klass,
Symbol* name,
Symbol* signature,
TRAPS
) {
Method* result_oop = klass->uncached_lookup_method(name, signature);
result = methodHandle(THREAD, result_oop);
// 循环查找方法的实现,不会查找静态方法
while (!result.is_null() && result->is_static() && result->method_holder()->super() != NULL) {
KlassHandle super_klass = KlassHandle(THREAD, result->method_holder()->super());
result = methodHandle(THREAD, super_klass->uncached_lookup_method(name, signature));
} // 当从拥有Itable的类或父类中找到接口中方法的实现时,result不为NULL,
// 否则为NULL,这时候就要查找默认的方法实现了,这也算是一种实现
if (result.is_null()) {
Array<Method*>* default_methods = InstanceKlass::cast(klass())->default_methods();
if (default_methods != NULL) {
result = methodHandle(InstanceKlass::find_method(default_methods, name, signature));
}
}
}
如上在查找默认方法实现时会调用find_method()函数,此函数在之前介绍invokevirtual字节码指令的解析过程时详细介绍过,这里不再介绍。
在LinkResolver::runtime_resolve_interface_method()函数的最后有可能调用CallInfo::set_interface()或CallInfo::set_virtual()函数,调用这两个函数就是将查找到的信息保存到CallInfo实例中。最终会在InterpreterRuntime::resolve_invoke()函数中根据CallInfo实例中保存的信息更新ConstantPoolCacheEntry相关的信息,如下:
switch (info.call_kind()) {
// ...
case CallInfo::itable_call:
cache_entry(thread)->set_itable_call(
bytecode,
info.resolved_method(),
info.itable_index());
break;
default: ShouldNotReachHere();
}
当CallInfo中保存的是itable的分派信息时,调用set_itable_call()函数,这个函数的实现如下:
void ConstantPoolCacheEntry::set_itable_call(
Bytecodes::Code invoke_code,
methodHandle method,
int index
) {
assert(invoke_code == Bytecodes::_invokeinterface, "");
InstanceKlass* interf = method->method_holder();
// interf一定是接口,而method一定是非final方法
set_f1(interf); // 对于itable,_f1保存的是表示接口的InstanceKlass
set_f2(index); // 对于itable,_f2保存的是itable索引
set_method_flags(as_TosState(method->result_type()),
0, // no option bits
method()->size_of_parameters());
set_bytecode_1(Bytecodes::_invokeinterface);
}
使用CallInfo实例中的信息更新ConstantPoolCacheEntry中的信息即可。
推荐阅读:
第2篇-JVM虚拟机这样来调用Java主类的main()方法
第13篇-通过InterpreterCodelet存储机器指令片段
第20篇-加载与存储指令之ldc与_fast_aldc指令(2)
第21篇-加载与存储指令之iload、_fast_iload等(3)
第34篇-解析invokeinterface字节码指令的更多相关文章
- 第32篇-解析interfacevirtual字节码指令
在前面介绍invokevirtual指令时,如果判断出ConstantPoolCacheEntry中的_indices字段的_f2属性的值为空,则认为调用的目标方法没有连接,也就是没有向Constan ...
- lua源码学习篇四:字节码指令
在llimits.h文件中定义了指令的类型.其实就是32个字节. typedef lu_int32 Instruction; 上节说到变量最终会存入proto的数组k中,返回的索引放在expdesc ...
- 第36篇-return字节码指令
方法返回的字节码相关指令如下表所示. 0xac ireturn 从当前方法返回int 0xad lreturn 从当前方法返回long 0xae freturn 从当前方法返回float 0xaf d ...
- 【JVM源码解析】模板解释器解释执行Java字节码指令(上)
本文由HeapDump性能社区首席讲师鸠摩(马智)授权整理发布 第17章-x86-64寄存器 不同的CPU都能够解释的机器语言的体系称为指令集架构(ISA,Instruction Set Archit ...
- 硬核万字长文,深入理解 Java 字节码指令(建议收藏)
Java 字节码指令是 JVM 体系中非常难啃的一块硬骨头,我估计有些读者会有这样的疑惑,"Java 字节码难学吗?我能不能学会啊?" 讲良心话,不是我谦虚,一开始学 Java 字 ...
- 从字节码指令看重写在JVM中的实现
Java是解释执行的.包含动态链接的特性.都给解析或执行期间提供了非常多灵活扩展的空间.面向对象语言的继承.封装和多态的特性,在JVM中是怎样进行编译.解析,以及通过字节码指令怎样确定方法调用的版本号 ...
- Class 文件结构及深入字节码指令
JVM的无关性 与平台无关性是建立在操作系统上,虚拟机厂商提供了许多可以运行在各种不同平台的虚拟机,它们都可以载入和执行字节码,从而实现程序的“一次编写,到处运行” https://www.oracl ...
- invokedynamic字节码指令
1. 方法引用和invokedynamic invokedynamic是jvm指令集里面最复杂的一条.本文将从高观点的角度下分析invokedynamic指令是如何实现方法引用(Method refe ...
- [四] java虚拟机JVM编译器编译代码简介 字节码指令实例 代码到底编译成了什么形式
前言简介 前文已经对虚拟机进行过了简单的介绍,并且也对class文件结构,以及字节码指令进行了详尽的说明 想要了解JVM的运行机制,以及如何优化你的代码,你还需要了解一下,java编译器到底是 ...
随机推荐
- Java一般命名规范
一.项目名称 最好用英文,所有单词全部用小写,如testjavaproject.studentmanagement等,当然也也可以用中文,如"学生管理系统"等. 二.Java pr ...
- C语言中的符号重载
摘自<C专家编程>第二章37页 C语言中符号的重载 符号 意义 static 在函数内部,表示该变量的值在各个调用间一直保持延续性在函数这一级,表示 ...
- java的split方法中的regex参数
我们需要以|进行分割,为了匹配|本身,正则中采用\|进行转义,而Java中\也表示转义,从java到正则需要必须使用\\|进行转义,即split中的\\表示正则的转义.
- 改Jupyter Notebook的默认工作路径?
如何更改Jupyter Notebook的默认工作路径? 1.在cmd中输入命令使Jupyter产生配置文件:Jupyter_notebook_config.py jupyter notebook - ...
- git tag标签
列出标签 # 默认按字母排序显示 $ git tag # 模糊匹配查找标签 $ git tag -l "v2.8.5*" 创建标签 # 创建附注标签 $ git tag -a v1 ...
- Docker系列(28)- 自定义网络
自定义网络 网络模式 bridge:桥接docker(默认,自己创建也可以使用bridge模式) none:不配置网络 host:和宿主机共享网络 container:容器网络联通!(用的少!局限性大 ...
- Jenkins无法登陆解决方案
Jenkins-2.204.1 版本 创建jenkins用户时,没填full name,且选择了使用系统的admin登录或者是admin登录只是改了admin的登录密码导致登录不上去(Invalid ...
- requestAnimationFrame 切换页面问题
requestAnimationFrame 切换页面时, 之前定时的内容还会继续执行. 所以 要注意处理动画函数内容,否则会出现死循环. 遇到的问题: 我在两个页面都有使用 requestAnimat ...
- python列表底层实现原理
Python 列表的数据结构是怎么样的? 书上说的是:列表实现可以是数组和链表.顺序表是怎么回事?顺序表一般是数组. 列表是一个线性的集合,它允许用户在任何位置插入.删除.访问和替换元素.列表实现是基 ...
- 在CentOS 6中安装和配置OrientDB社区版
OrientDB概述: OrientDB是一个开源NoSQL非关系型数据库管理系统. NoSQL数据库提供了一种用于存储和检索引用除表式数据之外的数据(例如文档数据或图形数据)的NO关系或非关系数据的 ...