本文由 HeapDump 性能社区首席讲师鸠摩(马智)受权整顿公布
第 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 中的信息即可。
第 35 篇 - 办法调用指令之 invokespecial 与 invokestatic 字
这一篇将具体介绍 invokespecial 和 invokestatic 字节码指令的汇编实现逻辑
1、invokespecial 指令
invokespecial 指令的模板定义如下:
def(Bytecodes::_invokespecial , ubcp|disp|clvm|____, vtos, vtos, invokespecial , f1_byte);
生成函数为 invokespecial(),生成的汇编代码如下:
0x00007fffe1022250: mov %r13,-0x38(%rbp)
0x00007fffe1022254: movzwl 0x1(%r13),%edx
0x00007fffe1022259: mov -0x28(%rbp),%rcx
0x00007fffe102225d: shl $0x2,%edx
0x00007fffe1022260: mov 0x10(%rcx,%rdx,8),%ebx
// 获取 ConstantPoolCacheEntry 中 indices[b2,b1,constant pool index]中的 b1
0x00007fffe1022264: shr $0x10,%ebx
0x00007fffe1022267: and $0xff,%ebx
// 查看 invokespecial=183 的 bytecode 是否曾经连贯,如果曾经连贯就进行跳转
0x00007fffe102226d: cmp $0xb7,%ebx
0x00007fffe1022273: je 0x00007fffe1022312
// ... 省略调用 InterpreterRuntime::resolve_invoke()函数
// 对 invokespecial=183 的 bytecode 进行连贯,// 因为字节码指令还没有连贯
// 将 invokespecial x 中的 x 加载到 %edx 中
0x00007fffe1022306: movzwl 0x1(%r13),%edx
// 将 ConstantPoolCache 的首地址存储到 %rcx 中
0x00007fffe102230b: mov -0x28(%rbp),%rcx
// %edx 中存储的是 ConstantPoolCacheEntry 项的索引,转换为字偏移
0x00007fffe102230f: shl $0x2,%edx
// 获取 ConstantPoolCache::_f1 属性的值
0x00007fffe1022312: mov 0x18(%rcx,%rdx,8),%rbx
// 获取 ConstantPoolCache::_flags 属性的值
0x00007fffe1022317: mov 0x28(%rcx,%rdx,8),%edx
// 将 flags 挪动到 ecx 中
0x00007fffe102231b: mov %edx,%ecx
// 从 flags 中取出参数大小
0x00007fffe102231d: and $0xff,%ecx
// 获取到 recv,%rcx 中保留的是参数大小,最终计算为 %rsp+%rcx*8-0x8,// flags 中的参数大小可能对实例办法来说,曾经包含了 recv 的大小
// 如调用实例办法的第一个参数是 this(recv)
0x00007fffe1022323: mov -0x8(%rsp,%rcx,8),%rcx
// 从 flags 中获取 return type,也就是从_flags 的高 4 位保留的 TosState
0x00007fffe1022328: shr $0x1c,%edx
// 将 TemplateInterpreter::invoke_return_entry 地址存储到 %r10
0x00007fffe102232b: movabs $0x7ffff73b6380,%r10
// 找到对应 return type 的 invoke_return_entry 的地址
0x00007fffe1022335: mov (%r10,%rdx,8),%rdx
// 通过 invokespecial 指令调用函数后的返回地址
0x00007fffe1022339: push %rdx
// 空值查看
0x00007fffe102233a: cmp (%rcx),%rax
// ...
// 设置调用者栈顶
0x00007fffe102235c: lea 0x8(%rsp),%r13
// 向栈中 last_sp 的地位保留调用者栈顶
0x00007fffe1022361: mov %r13,-0x10(%rbp)
// 跳转到 Method::_from_interpretered_entry 入口去执行
0x00007fffe1022365: jmpq *0x58(%rbx)
invokespecial 指令在调用 private 和构造方法时,不须要动静散发。在这个字节码指令解析实现后,ConstantPoolCacheEntry 中的 \_f1 指向指标办法的 Method 实例,\_f2 没有应用,所以如上汇编的逻辑非常简单,这里不再过多介绍。
2、invokestatic 指令
invokestatic 指令的模板定义如下:
def(Bytecodes::_invokestatic , ubcp|disp|clvm|____, vtos, vtos, invokestatic , f1_byte);
生成函数为 invokestatic(),生成的汇编代码如下:
0x00007fffe101c030: mov %r13,-0x38(%rbp)
0x00007fffe101c034: movzwl 0x1(%r13),%edx
0x00007fffe101c039: mov -0x28(%rbp),%rcx
0x00007fffe101c03d: shl $0x2,%edx
0x00007fffe101c040: mov 0x10(%rcx,%rdx,8),%ebx
0x00007fffe101c044: shr $0x10,%ebx
0x00007fffe101c047: and $0xff,%ebx
0x00007fffe101c04d: cmp $0xb8,%ebx
// 查看 invokestatic=184 的 bytecode 是否曾经连贯,如果曾经连贯就进行跳转
0x00007fffe101c053: je 0x00007fffe101c0f2
// 调用 InterpreterRuntime::resolve_invoke()函数对 invokestatic=184 的
// 的 bytecode 进行连贯,因为字节码指令还没有连贯
// ... 省略了解析 invokestatic 的汇编代码
// 将 invokestatic x 中的 x 加载到 %edx 中
0x00007fffe101c0e6: movzwl 0x1(%r13),%edx
// 将 ConstantPoolCache 的首地址存储到 %rcx 中
0x00007fffe101c0eb: mov -0x28(%rbp),%rcx
// %edx 中存储的是 ConstantPoolCacheEntry 项的索引,转换为字偏移
0x00007fffe101c0ef: shl $0x2,%edx
// 获取 ConstantPoolCache::_f1 属性的值
0x00007fffe101c0f2: mov 0x18(%rcx,%rdx,8),%rbx
// 获取 ConstantPoolCache::_flags 属性的值
0x00007fffe101c0f7: mov 0x28(%rcx,%rdx,8),%edx
// 从 flags 中获取 return type,也就是从_flags 的高 4 位保留的 TosState
0x00007fffe101c0fb: shr $0x1c,%edx
// 将 TemplateInterpreter::invoke_return_entry 地址存储到 %r10
0x00007fffe101c0fe: movabs $0x7ffff73b5d00,%r10
// 找到对应 return type 的 invoke_return_entry 的地址
0x00007fffe101c108: mov (%r10,%rdx,8),%rdx
// 通过 invokespecial 指令调用函数后的返回地址
0x00007fffe101c10c: push %rdx
// 设置调用者栈顶
0x00007fffe101c10d: lea 0x8(%rsp),%r13
// 向栈中 last_sp 的地位保留调用者栈顶
0x00007fffe101c112: mov %r13,-0x10(%rbp)
// 跳转到 Method::_from_interpretered_entry 入口去执行
0x00007fffe101c116: jmpq *0x58(%rbx)
invokespecial 指令在调用静态方法时,不须要动静散发。在这个字节码指令解析实现后,ConstantPoolCacheEntry 中的 \_f1 指向指标办法的 Method 实例,\_f2 没有应用,所以如上汇编的逻辑非常简单,这里不再过多介绍。
对于 invokestatic 与 invokespecial 的解析过程这里就不再过多介绍了,有趣味的可从 LinkResolver::resolve_invoke()函数查看具体的解析过程。
第 36 篇 - 办法返回指令之 return
办法返回的字节码相干指令如下表所示。
模板定义如下:
def(Bytecodes::_ireturn , ____|disp|clvm|____, itos, itos, _return , itos);
def(Bytecodes::_lreturn , ____|disp|clvm|____, ltos, ltos, _return , ltos);
def(Bytecodes::_freturn , ____|disp|clvm|____, ftos, ftos, _return , ftos);
def(Bytecodes::_dreturn , ____|disp|clvm|____, dtos, dtos, _return , dtos);
def(Bytecodes::_areturn , ____|disp|clvm|____, atos, atos, _return , atos);
def(Bytecodes::_return , ____|disp|clvm|____, vtos, vtos, _return , vtos);
def(Bytecodes::_return_register_finalizer , ____|disp|clvm|____, vtos, vtos, _return , vtos);
生成函数都为 TemplateTable::\_return()。然而如果是 Object 对象的构造方法中的 return 指令,那么这个指令还可能会被重写为 \_return\_register\_finalizer 指令。
生成的 return 字节码指令对应的汇编代码如下:
第 1 局部
// 将 JavaThread::do_not_unlock_if_synchronized 属性存储到 %dl 中
0x00007fffe101b770: mov 0x2ad(%r15),%dl
// 重置 JavaThread::do_not_unlock_if_synchronized 属性值为 false
0x00007fffe101b777: movb $0x0,0x2ad(%r15)
// 将 Method* 加载到 %rbx 中
0x00007fffe101b77f: mov -0x18(%rbp),%rbx
// 将 Method::_access_flags 加载到 %ecx 中
0x00007fffe101b783: mov 0x28(%rbx),%ecx
// 查看 Method::flags 是否蕴含 JVM_ACC_SYNCHRONIZED
0x00007fffe101b786: test $0x20,%ecx
// 如果办法不是同步办法,跳转到 ----unlocked----
0x00007fffe101b78c: je 0x00007fffe101b970
// 如果在 %dl 寄存器中存储的_do_not_unlock_if_synchronized 的值不为 0,// 则跳转到 no_unlock,示意不要开释和锁相干的资源
0x00007fffe101b792: test $0xff,%dl
0x00007fffe101b795: jne
0x00007fffe101ba90 // 跳转到 ----no_unlock---- 处
在 JavaThread 类中定义了一个属性 \_do\_not\_unlock\_if\_synchronized,这个值示意在抛出异样的状况下不要开释 receiver(在非静态方法调用的状况下,咱们总是会将办法解析到某个对象上,这个对象就是这里的 receiver,也可称为接收者),此值仅在解释执行的状况下才会起作用。初始的时候会初始化为 false。在如上汇编中能够看到,当 \_do\_not\_unlock\_if\_synchronized 的值为 true 时,示意不须要开释 receiver,所以尽管以后是同步办法,然而却间接调用到了 no_unlock 处。
第 2 局部
如果执行如下汇编代码,则示意 %dl 寄存器中存储的 \_do\_not\_unlock\_if_synchronized 的值为 0,须要执行开释锁的操作。
// 将之前字节码指令执行的后果存储到表达式栈顶,// 因为 return 不须要返回执行后果,所以不须要设置返回值等信息,// 最终在这里没有生成任何 push 指令
// 将 BasicObjectLock 存储到 %rsi 中,因为 %rsi 在调用 C ++ 函数时可做为
// 第 2 个参数传递,所以如果要调用 unlock_object 就能够传递此值
0x00007fffe101b79b: lea -0x50(%rbp),%rsi
// 获取 BasicObjectLock::obj 属性地址存储到 %rax 中
0x00007fffe101b79f: mov 0x8(%rsi),%rax
// 如果不为 0,则跳转到 unlock 处,因为不为 0,示意
// 这个 obj 有指向的锁对象,须要进行开释锁的操作
0x00007fffe101b7a3: test %rax,%rax
0x00007fffe101b7a6: jne 0x00007fffe101b8a8 // 跳转到 ----unlock---- 处
// 如果是其它的 return 指令,则因为之前通过 push 指令将后果保留在
// 表达式栈上,所以当初可通过 pop 将表达式栈上的后果弹出到对应寄存器中
第 1 个指令的 -0x50(%rbp)指向了第 1 个 BasicObjectLock 对象,其中的 sizeof(BasicObjectLock)的值为 16,也就是 16 个字节。在之前咱们介绍栈帧的时候介绍过 Java 解释栈的构造,如下:
假如以后的栈帧中有 2 个锁对象,则会在栈帧中存储 2 个 BasicObjectLock 对象,BasicObjectLock 中有 2 个属性,\_lock 和 \_obj,别离占用 8 字节。布局如下图所示。
因为 return 字节码指令负责要开释的是加 synchronized 关键字的、解释执行的 Java 办法,所以为 synchronized 关键字建设的第 1 个锁对象存储在离以后栈帧最靠近栈底的中央,也就是上图中灰色局部,而其它锁对象咱们临时不必管。灰色局部示意的 BasicObjectLock 的地址通过 -0x50(%rbp)就能获取到,而后对其中的 \_lock 和 \_obj 属性进行操作。
因为当初还没有介绍锁相干的常识,所以这里不做过多介绍,在前面介绍完锁相干常识后还会具体介绍。
第 3 局部
在变量 throw\_monitor\_exception 为 true 的状况下,通过调用 call\_VM()函数生成抛出锁状态异样的汇编代码,这些汇编代码次要是为了执行 C ++ 函数 InterpreterRuntime::throw\_illegal\_monitor\_state\_exception()。实现执行后还会执行由 should\_not\_reach\_here()函数生成的汇编代码。
在变量 throw\_monitor\_exception 为 false 并且 install\_monitor\_exception 为 true 的状况下,通过调用 call\_VM()函数生成汇编代码来执行 C ++ 函数 InterpreterRuntime::new\_illegal\_monitor\_state_exception()。最初跳转到 unlocked 处执行。
第 4 局部
在 InterpreterMacroAssembler::remove\_activation()函数中,bind 完 unlock 后就会调用 InterpreterMacroAssembler::unlock\_object()函数生成如下的汇编代码。InterpreterMacroAssembler::unlock_object()函数的作用如下:
Unlocks an object. Used in monitorexit bytecode and remove_activation. Throws an IllegalMonitorException if object is not locked by current thread.
生成的汇编代码如下:
// **** unlock ****
// ============ 调用 InterpreterMacroAssembler::unlock_object()函数生成如下的汇编代码 ==================
// 将 %r13 存储到栈中,避免异样毁坏了 %r13 寄存器中的值
0x00007fffe101b8a8: mov %r13,-0x38(%rbp)
// 将 BasicObjectLock::_lock 的地址存储到 %rax 寄存器中
0x00007fffe101b8ac: lea (%rsi),%rax
// 将 BasicObjectLock::_obj 存储到 %rcx 寄存器中
0x00007fffe101b8af: mov 0x8(%rsi),%rcx
// 将 BasicObjectLock::_obj 的值设置为 NULL,示意开释锁操作
0x00007fffe101b8b3: movq $0x0,0x8(%rsi)
// ---------- 当 UseBiasedLocking 的值为 true 时,调用 MacroAssembler::biased_locking_exit()生成如下的汇编代码 ------------
// 从 BasicObjectLock::_obj 对象中取出 mark 属性值并相与
0x00007fffe101b8bb: mov (%rcx),%rdx
0x00007fffe101b8be: and $0x7,%rdx
// 如果 BasicObjectLock::_obj 指向的 oop 的 mark 属性后 3 位是偏差锁的状态,则跳转到 ---- done ----
0x00007fffe101b8c2: cmp $0x5,%rdx
0x00007fffe101b8c6: je 0x00007fffe101b96c
// ------------------------ 完结调用 MacroAssembler::biased_locking_exit()生成的汇编代码 ---------------------
// 将 BasicObjectLock::_lock 这个 oop 对象的_displaced_header 属性值取出
0x00007fffe101b8cc: mov (%rax),%rdx
// 判断一下是否为锁的重入,如果是锁的重入,则跳转到 ---- done ----
0x00007fffe101b8cf: test %rdx,%rdx
0x00007fffe101b8d2: je 0x00007fffe101b96c
// 让 BasicObjectLock::_obj 的那个 oop 的 mark 复原为
// BasicObjectLock::_lock 中保留的原对象头
0x00007fffe101b8d8: lock cmpxchg %rdx,(%rcx)
// 如果为 0,则示意锁的重入,跳转到 ---- done ---- ????
0x00007fffe101b8dd: je 0x00007fffe101b96c
// 让 BasicObjectLock::_obj 指向 oop,这个 oop 的对象头曾经替换为了 BasicObjectLock::_lock 中保留的对象头
0x00007fffe101b8e3: mov %rcx,0x8(%rsi)
// ----------- 调用 call_VM()函数生成汇编代码来执行 C ++ 函数 InterpreterRuntime::monitorexit()----------------
0x00007fffe101b8e7: callq 0x00007fffe101b8f1
0x00007fffe101b8ec: jmpq 0x00007fffe101b96c
0x00007fffe101b8f1: lea 0x8(%rsp),%rax
0x00007fffe101b8f6: mov %r13,-0x38(%rbp)
0x00007fffe101b8fa: mov %r15,%rdi
0x00007fffe101b8fd: mov %rbp,0x200(%r15)
0x00007fffe101b904: mov %rax,0x1f0(%r15)
0x00007fffe101b90b: test $0xf,%esp
0x00007fffe101b911: je 0x00007fffe101b929
0x00007fffe101b917: sub $0x8,%rsp
0x00007fffe101b91b: callq 0x00007ffff66b3d22
0x00007fffe101b920: add $0x8,%rsp
0x00007fffe101b924: jmpq 0x00007fffe101b92e
0x00007fffe101b929: callq 0x00007ffff66b3d22
0x00007fffe101b92e: movabs $0x0,%r10
0x00007fffe101b938: mov %r10,0x1f0(%r15)
0x00007fffe101b93f: movabs $0x0,%r10
0x00007fffe101b949: mov %r10,0x200(%r15)
0x00007fffe101b950: cmpq $0x0,0x8(%r15)
0x00007fffe101b958: je 0x00007fffe101b963
0x00007fffe101b95e: jmpq 0x00007fffe1000420
0x00007fffe101b963: mov -0x38(%rbp),%r13
0x00007fffe101b967: mov -0x30(%rbp),%r14
0x00007fffe101b96b: retq
// ------------------------ 完结 call_VM()函数调用生成的汇编代码 --------------------------------
// **** done ****
0x00007fffe101b96c: mov -0x38(%rbp),%r13
0x00007fffe101b970: mov -0x40(%rbp),%rsi
// ========== 完结调用 InterpreterMacroAssembler::unlock_object()函数生成如下的汇编代码 ============
第 5 局部
// 如果是其它的 return 指令,则因为之前通过 push 指令将后果保留在
// 表达式栈上,所以当初可通过 pop 将表达式栈上的后果弹出到对应寄存器中
// **** unlocked ****
// 在执行这里的代码时,示意以后的栈中没有相干的锁,也就是
// 相干的锁对象曾经全副开释
// **** restart ****
// 检查一下,是否所有的锁都曾经开释了
// %rsi 指向以后栈中最靠栈顶的 BasicObjectLock
0x00007fffe101b970: mov -0x40(%rbp),%rsi
// %rbx 指向以后栈中最靠栈底的 BasicObjectLock
0x00007fffe101b974: lea -0x40(%rbp),%rbx
// 跳转到 ----entry----
0x00007fffe101b978: jmpq 0x00007fffe101ba8b
第 6 局部
执行如下代码,会通过调用 call\_VM()函数来生成调用 InterpreterRuntime::throw\_illegal\_monitor\_state_exception()函数的代码:
// **** exception ****
// Entry already locked, need to throw exception
// 当 throw_monitor_exception 的值为 true 时,执行如下 2 个函数生成的汇编代码:// 执行 call_VM()函数生成的汇编代码,就是调用 C ++ 函数 InterpreterRuntime::throw_illegal_monitor_state_exception()
// 执行 should_not_reach_here()函数生成的汇编代码
// 当 throw_monitor_exception 的值为 false,执行如下汇编:// 执行调用 InterpreterMacroAssembler::unlock_object()函数生成的汇编代码
// install_monitor_exception 的值为 true 时,执行 call_VM()函数生成的汇编代码,就是调用 C ++ 函数 InterpreterRuntime::new_illegal_monitor_state_exception()
// 无条件跳转到 ----restart ----
第 7 局部
// **** loop ****
// 将 BasicObjectLock::obj 与 NULL 比拟,如果不相等,则跳转到 ----exception----
0x00007fffe101ba79: cmpq $0x0,0x8(%rsi)
0x00007fffe101ba81: jne 0x00007fffe101b97d // 则跳转到 ----exception----
第 8 局部
// **** entry ****
// 0x10 为 BasicObjectLock,找到下一个 BasicObjectLock
0x00007fffe101ba87: add $0x10,%rsi
// 查看是否达到了锁对象存储区域的底部
0x00007fffe101ba8b: cmp %rbx,%rsi
// 如果不相等,跳转到 loop
0x00007fffe101ba8e: jne 0x00007fffe101ba79 // 跳转到 ----loop----
第 9 局部
// **** no_unlock ****
// 省略 jvmti support
// 将 -0x8(%rbp)处保留的 old stack pointer(saved rsp)取出来放到 %rbx 中
0x00007fffe101bac7: mov -0x8(%rbp),%rbx
// 移除栈帧
// leave 指令相当于:// mov %rbp, %rsp
// pop %rbp
0x00007fffe101bacb: leaveq
// 将返回地址弹出到 %r13 中
0x00007fffe101bacc: pop %r13
// 设置 %rsp 为调用者的栈顶值
0x00007fffe101bace: mov %rbx,%rsp
0x00007fffe101bad1: jmpq *%r13
其中的解释办法返回地址为 return address,因为以后是 C ++ 函数调用 Java,所以这个返回地址其实是 C ++ 函数的返回地址,咱们不须要思考。
整个的调用转换如下图所示。
其中的红色局部示意终结这个流程。
在 return 字节码指令中会波及到锁开释的流程,所以下面的流程图看起来会简单一些,等咱们介绍完锁相干常识后会再次介绍 return 指令,这里不再过多介绍。
第 37 篇 - 复原调用者栈帧例程 Interpreter::\_invoke\_return_entry
咱们在之前介绍过 return 字节码指令的执行逻辑,这个字节码指令只会执行开释锁和退出以后栈帧的操作,然而当控制权转移给调用者时,还须要复原调用者的栈帧状态,如让 %r13 指向 bcp、%r14 指向局部变量表等,另外还须要弹出压入的实参、跳转到调用者的下一个字节码指令继续执行,而这所有操作都是由 Interpreter::\_return\_entry 例程负责的。这个例程在之前介绍 invokevirtual 和 invokeinterface 等字节码指令时介绍过,当应用这些字节码指令调用办法时,会依据办法的返回类型压入 Interpreter::\_return\_entry 一维数组中保留的对应例程地址,这样 return 字节码指令执行实现后就会执行这段例程。
在 invokevirtual 和 invokeinterface 等字节码指令中通过调用如下函数获取对应的例程入口:
address* TemplateInterpreter::invoke_return_entry_table_for(Bytecodes::Code code) {switch (code) {
case Bytecodes::_invokestatic:
case Bytecodes::_invokespecial:
case Bytecodes::_invokevirtual:
case Bytecodes::_invokehandle:
return Interpreter::invoke_return_entry_table();
case Bytecodes::_invokeinterface:
return Interpreter::invokeinterface_return_entry_table();
default:
fatal(err_msg("invalid bytecode: %s", Bytecodes::name(code)));
return NULL;
}
}
能够看到 invokeinterface 字节码从 Interpreter::\_invokeinterface\_return\_entry 数组中获取对应的例程,而其它的从 Interpreter::\_invoke\_return\_entry 一维数组中获取。如下:
address TemplateInterpreter::_invoke_return_entry[TemplateInterpreter::number_of_return_addrs];
address TemplateInterpreter::_invokeinterface_return_entry[TemplateInterpreter::number_of_return_addrs];
当返回一维数组后,会依据办法返回类型进一步确定例程入口地址。上面咱们就看一下这些例程的生成过程。
TemplateInterpreterGenerator::generate\_all()函数中会生成 Interpreter::\_return_entry 入口,如下:
{CodeletMark cm(_masm, "invoke return entry points");
const TosState states[] = {itos, itos, itos, itos, ltos, ftos, dtos, atos, vtos};
const int invoke_length = Bytecodes::length_for(Bytecodes::_invokestatic); // invoke_length=3
const int invokeinterface_length = Bytecodes::length_for(Bytecodes::_invokeinterface); // invokeinterface=5
for (int i = 0; i < Interpreter::number_of_return_addrs; i++) { // number_of_return_addrs = 9
TosState state = states[i]; // TosState 是枚举类型
Interpreter::_invoke_return_entry[i] = generate_return_entry_for(state, invoke_length, sizeof(u2));
Interpreter::_invokeinterface_return_entry[i] = generate_return_entry_for(state, invokeinterface_length, sizeof(u2));
}
}
除 invokedynamic 字节码指令外,其它的办法调用指令在解释执行实现后都须要调用由 generate\_return\_entry\_for()函数生成的例程,生成例程的 generate\_return\_entry\_for()函数实现如下:
address TemplateInterpreterGenerator::generate_return_entry_for(TosState state, int step, size_t index_size) {
// Restore stack bottom in case 万一 i2c adjusted stack
__ movptr(rsp, Address(rbp, frame::interpreter_frame_last_sp_offset * wordSize)); // interpreter_frame_last_sp_offset=-2
// and NULL it as marker that esp is now tos until next java call
__ movptr(Address(rbp, frame::interpreter_frame_last_sp_offset * wordSize), (int32_t)NULL_WORD);
__ restore_bcp();
__ restore_locals();
// ...
const Register cache = rbx;
const Register index = rcx;
__ get_cache_and_index_at_bcp(cache, index, 1, index_size);
const Register flags = cache;
__ movl(flags, Address(cache, index, Address::times_ptr, ConstantPoolCache::base_offset() + ConstantPoolCacheEntry::flags_offset()));
__ andl(flags, ConstantPoolCacheEntry::parameter_size_mask);
__ lea(rsp, Address(rsp, flags, Interpreter::stackElementScale()) ); // 栈元素标量为 8
__ dispatch_next(state, step);
return entry;
}
依据 state 的不同(办法的返回类型的不同),会在抉择执行调用者办法的下一个字节码指令时,决定要从字节码指令的哪个入口处开始执行。咱们看一下,当传递的 state 为 itos(也就是当办法的返回类型为 int 时)时生成的汇编代码如下:
// 将 -0x10(%rbp)存储到 %rsp 后,置空 -0x10(%rbp)
0x00007fffe1006ce0: mov -0x10(%rbp),%rsp // 更改 rsp
0x00007fffe1006ce4: movq $0x0,-0x10(%rbp) // 更改栈中特定地位的值
// 复原 bcp 和 locals,使 %r14 指向本地变量表,%r13 指向 bcp
0x00007fffe1006cec: mov -0x38(%rbp),%r13
0x00007fffe1006cf0: mov -0x30(%rbp),%r14
// 获取 ConstantPoolCacheEntry 的索引并加载到 %ecx
0x00007fffe1006cf4: movzwl 0x1(%r13),%ecx
// 获取栈中 -0x28(%rbp)的 ConstantPoolCache 并加载到 %ecx
0x00007fffe1006cf9: mov -0x28(%rbp),%rbx
// shl 是逻辑左移,获取字偏移
0x00007fffe1006cfd: shl $0x2,%ecx
// 获取 ConstantPoolCacheEntry 中的_flags 属性值
0x00007fffe1006d00: mov 0x28(%rbx,%rcx,8),%ebx
// 获取_flags 中的低 8 位中保留的参数大小
0x00007fffe1006d04: and $0xff,%ebx
// lea 指令将地址加载到内存寄存器中,也就是复原调用办法之前栈的样子
0x00007fffe1006d0a: lea (%rsp,%rbx,8),%rsp
// 跳转到下一指令执行
0x00007fffe1006d0e: movzbl 0x3(%r13),%ebx
0x00007fffe1006d13: add $0x3,%r13
0x00007fffe1006d17: movabs $0x7ffff73b7ca0,%r10
0x00007fffe1006d21: jmpq *(%r10,%rbx,8)
如上汇编代码的逻辑非常简单,这里不再过多介绍。
第 38 篇 - 解释办法之间的调用小实例
这一篇咱们介绍一下解释执行的 main()办法调用解析执行的 add()办法的小实例,这个例子如下:
package com.classloading;
public class TestInvokeMethod {public int add(int a, int b) {return a + b;}
public static void main(String[] args) {TestInvokeMethod tim = new TestInvokeMethod();
tim.add(2, 3);
}
}
通过 Javac 编译器编译为字节码文件,如下:
Constant pool:
#1 = Methodref #5.#16 // java/lang/Object."<init>":()V
#2 = Class #17 // com/classloading/TestInvokeMethod
#3 = Methodref #2.#16 // com/classloading/TestInvokeMethod."<init>":()V
#4 = Methodref #2.#18 // com/classloading/TestInvokeMethod.add:(II)I
#5 = Class #19 // java/lang/Object
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 add
#11 = Utf8 (II)I
#12 = Utf8 main
#13 = Utf8 ([Ljava/lang/String;)V
#14 = Utf8 SourceFile
#15 = Utf8 TestInvokeMethod.java
#16 = NameAndType #6:#7 // "<init>":()V
#17 = Utf8 com/classloading/TestInvokeMethod
#18 = NameAndType #10:#11 // add:(II)I
#19 = Utf8 java/lang/Object
{public com.classloading.TestInvokeMethod();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public int add(int, int);
descriptor: (II)I
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iload_2
2: iadd
3: ireturn
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: new #2 // class com/classloading/TestInvokeMethod
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: aload_1
9: iconst_2
10: iconst_3
11: invokevirtual #4 // Method add:(II)I
14: pop
15: return
}
上面分几局部介绍调用相干的内容。
1.C++ 函数调用 main()办法
当初咱们从字节码索引为 8 的 aload_1 开始看,此时的栈帧状态如下:
因为 aload\_1 的 tos\_out 为 atos,所以在栈顶缓存的寄存器中会缓存有 TestInvokeMethod 实例的地址,当执行 iconst\_2 时,会从 atos 进入。iconst\_2 指令的汇编如下:
// aep
push %rax
jmpq // 跳转到上面那条指令执行
// ...
mov $0x2,%eax // 指令的汇编代码
因为 iconst\_2 的 tos\_out 为 itos,所以在进入下一个指令时,会从 iconst\_3 的 tos\_int 为 itos 中进入,如下:
// iep
push %rax
mov $0x3,%eax
接下来就是执行 invokevirtual 字节码指令了,此时的 2 曾经压入了表达式栈,而 3 在 %eax 寄存器中做为栈顶缓存,然而 invokevirtual 的 tos_in 为 vtos,所以从 invokevirtual 字节码指令的 iep 进入时会将 %eax 寄存器中的值也压入表达式栈中,最终的栈状态如下图所示。
2.main()办法调用 add()办法
invokevirtual 字节码指令在执行时,假如此字节码指令曾经解析实现,也就是对应的 ConstantPoolCacheEntry 中曾经保留了办法调用相干的信息,则执行的相干汇编代码如下:
0x00007fffe1021f90: mov %r13,-0x38(%rbp) // 将 bcp 保留到栈中
// invokevirtual x 中取出 x,也就是常量池索引存储到 %edx,// 其实这里曾经是 ConstantPoolCacheEntry 的 index,因为在类的连贯
// 阶段会对办法中特定的一些字节码指令进行重写
0x00007fffe1021f94: movzwl 0x1(%r13),%edx
// 将 ConstantPoolCache 的首地址存储到 %rcx
0x00007fffe1021f99: mov -0x28(%rbp),%rcx
// 左移 2 位,因为 %edx 中存储的是 ConstantPoolCacheEntry 索引,左移 2 位是因为
// ConstantPoolCacheEntry 占用 4 个字
0x00007fffe1021f9d: shl $0x2,%edx
// 计算 %rcx+%rdx*8+0x10,获取 ConstantPoolCacheEntry[_indices,_f1,_f2,_flags]中的_indices
// 因为 ConstantPoolCache 的大小为 0x16 字节,%rcx+0x10 定位
// 到第一个 ConstantPoolCacheEntry 的地位
// %rdx* 8 算进去的是绝对于第一个 ConstantPoolCacheEntry 的字节偏移
0x00007fffe1021fa0: mov 0x10(%rcx,%rdx,8),%ebx
// 获取 ConstantPoolCacheEntry 中 indices[b2,b1,constant pool index]中的 b2
0x00007fffe1021fa4: shr $0x18,%ebx
// 取出 indices 中含有的 b2,即 bytecode 存储到 %ebx 中
0x00007fffe1021fa7: and $0xff,%ebx
// 查看 182 的 bytecode 是否曾经连贯
0x00007fffe1021fad: cmp $0xb6,%ebx
// 如果连贯就进行跳转,跳转到 resolved
0x00007fffe1021fb3: je 0x00007fffe1022052
咱们间接看办法解析后的逻辑实现,如下:
// **** resolved ****
// resolved 的定义点,到这里阐明 invokevirtual 字节码曾经连贯
// 获取 ConstantPoolCacheEntry::_f2, 这个字段只对 virtual 有意义
// 在计算时,因为 ConstantPoolCacheEntry 在 ConstantPoolCache 之后保留,// 所以 ConstantPoolCache 为 0x10,而
// _f2 还要偏移 0x10,这样总偏移就是 0x20
// ConstantPoolCacheEntry::_f2 存储到 %rbx
0x00007fffe1022052: mov 0x20(%rcx,%rdx,8),%rbx
// ConstantPoolCacheEntry::_flags 存储到 %edx
0x00007fffe1022057: mov 0x28(%rcx,%rdx,8),%edx
// 将 flags 挪动到 ecx 中
0x00007fffe102205b: mov %edx,%ecx
// 从 flags 中取出参数大小
0x00007fffe102205d: and $0xff,%ecx
// 获取到 recv,%rcx 中保留的是参数大小,最终计算参数所须要的大小为 %rsp+%rcx*8-0x8,// flags 中的参数大小对实例办法来说,曾经包含了 recv 的大小
// 如调用实例办法的第一个参数是 this(recv)
0x00007fffe1022063: mov -0x8(%rsp,%rcx,8),%rcx // recv 保留到 %rcx
// 将 flags 存储到 r13 中
0x00007fffe1022068: mov %edx,%r13d
// 从 flags 中获取 return type,也就是从_flags 的高 4 位保留的 TosState
0x00007fffe102206b: shr $0x1c,%edx
// 将 TemplateInterpreter::invoke_return_entry 地址存储到 %r10
0x00007fffe102206e: movabs $0x7ffff73b6380,%r10
// %rdx 保留的是 return type,计算返回地址
// 因为 TemplateInterpreter::invoke_return_entry 是数组,// 所以要找到对应 return type 的入口地址
0x00007fffe1022078: mov (%r10,%rdx,8),%rdx
// 向栈中压入返回地址
0x00007fffe102207c: push %rdx
// 还原 ConstantPoolCacheEntry::_flags
0x00007fffe102207d: mov %r13d,%edx
// 还原 bcp
0x00007fffe1022080: mov -0x38(%rbp),%r13
执行完如上的代码后,曾经向相干的寄存器中存储了相干的值。相干的寄存器状态如下:
rbx: 存储的是 ConstantPoolCacheEntry::_f2 属性的值
rcx: 就是调用实例办法时的第一个参数 this
rdx: 存储的是 ConstantPoolCacheEntry::_flags 属性的值
栈的状态如下图所示。
须要留神的是 return address 也是一个例程的地址,是 TemplateInterpreter::invoke\_return\_entry 一维数组中类型为整数对应的下标存储的那个地址,因为调用 add()办法返回的是整数类型。如何得出 add()办法的返回类型呢?是从 ConstantPoolCacheEntry 的_flags 的 TosState 中得出的。
上面持续看 invokevirtual 字节码指令将要执行的汇编代码,如下:
// flags 存储到 %eax
0x00007fffe1022084: mov %edx,%eax
// 测试调用的办法是否为 final
0x00007fffe1022086: and $0x100000,%eax
// 如果不为 final 就间接跳转到 ----notFinal----
0x00007fffe102208c: je 0x00007fffe10220c0
// 通过 (%rcx) 来获取 receiver 的值,如果 %rcx 为空,则会引起 OS 异样
0x00007fffe1022092: cmp (%rcx),%rax
// 省略统计相干代码局部
// 设置调用者栈顶并保留
0x00007fffe10220b4: lea 0x8(%rsp),%r13
0x00007fffe10220b9: mov %r13,-0x10(%rbp)
// 跳转到 Method::_from_interpretered_entry 入口去执行
0x00007fffe10220bd: jmpq *0x58(%rbx)
执行 Method::\_from\_interpretered_entry 例程,这个例程在之前具体介绍过,执行实现后会为 add()办法创立栈帧,此时的栈状态如下图所示。
执行 iload\_0 与 iload\_1 指令,因为间断呈现了 2 个 iload,所以是 \_fast\_iload2,汇编如下:
movzbl 0x1(%r13),%ebx
neg %rbx
mov (%r14,%rbx,8),%eax
push %rax
movzbl 0x3(%r13),%ebx
neg %rbx
mov (%r14,%rbx,8),%eax
留神,只有第 1 个变量压入了栈,第 2 个则存储到 %eax 中做为栈顶缓存。
调用 iadd 指令,因为 tos_in 为 itos,所以汇编如下:
mov (%rsp),%edx
add $0x8,%rsp
add %edx,%eax
最初后果缓存在 %eax 中。
3. 退出 add()办法
执行 ireturn 字节码指令进行 add()办法的退栈操作。对于实例来说,执行的相干汇编代码如下:
// 将 JavaThread::do_not_unlock_if_synchronized 属性存储到 %dl 中
0x00007fffe101b770: mov 0x2ad(%r15),%dl
// 重置 JavaThread::do_not_unlock_if_synchronized 属性值为 false
0x00007fffe101b777: movb $0x0,0x2ad(%r15)
// 将 Method* 加载到 %rbx 中
0x00007fffe101b77f: mov -0x18(%rbp),%rbx
// 将 Method::_access_flags 加载到 %ecx 中
0x00007fffe101b783: mov 0x28(%rbx),%ecx
// 查看 Method::flags 是否蕴含 JVM_ACC_SYNCHRONIZED
0x00007fffe101b786: test $0x20,%ecx
// 如果办法不是同步办法,跳转到 ----unlocked----
0x00007fffe101b78c: je 0x00007fffe101b970
unlocked 处的汇编实现如下:
// 将 -0x8(%rbp)处保留的 old stack pointer(saved rsp)取出来放到 %rbx 中
0x00007fffe101bac7: mov -0x8(%rbp),%rbx
// 移除栈帧
// leave 指令相当于:// mov %rbp, %rsp
// pop %rbp
0x00007fffe101bacb: leaveq
// 将返回地址弹出到 %r13 中
0x00007fffe101bacc: pop %r13
// 设置 %rsp 为调用者的栈顶值
0x00007fffe101bace: mov %rbx,%rsp
0x00007fffe101bad1: jmpq *%r13
执行 leaveq 指令进行退栈操作,此时的栈状态如下图所示。
而后咱们就要弹出返回地址,跳转到 TemplateInterpreter::invoke\_return\_entry 数组中保留的相干地址去执行对应的例程了。
4. 执行返回例程
对于实例来说,传递的 state 为 itos 时生成的汇编代码如下:
// 将 -0x10(%rbp)存储到 %rsp 后,置空 -0x10(%rbp)
0x00007fffe1006ce0: mov -0x10(%rbp),%rsp // 更改 rsp
0x00007fffe1006ce4: movq $0x0,-0x10(%rbp) // 更改栈中特定地位的值
// 复原 bcp 和 locals,使 %r14 指向本地变量表,%r13 指向 bcp
0x00007fffe1006cec: mov -0x38(%rbp),%r13
0x00007fffe1006cf0: mov -0x30(%rbp),%r14
// 获取 ConstantPoolCacheEntry 的索引并加载到 %ecx
0x00007fffe1006cf4: movzwl 0x1(%r13),%ecx
// 获取栈中 -0x28(%rbp)的 ConstantPoolCache 并加载到 %ecx
0x00007fffe1006cf9: mov -0x28(%rbp),%rbx
// shl 是逻辑左移,获取字偏移
0x00007fffe1006cfd: shl $0x2,%ecx
// 获取 ConstantPoolCacheEntry 中的_flags 属性值
0x00007fffe1006d00: mov 0x28(%rbx,%rcx,8),%ebx
// 获取_flags 中的低 8 位中保留的参数大小
0x00007fffe1006d04: and $0xff,%ebx
// lea 指令将地址加载到内存寄存器中,也就是复原调用办法之前栈的样子
0x00007fffe1006d0a: lea (%rsp,%rbx,8),%rsp
// 跳转到下一指令执行
0x00007fffe1006d0e: movzbl 0x3(%r13),%ebx
0x00007fffe1006d13: add $0x3,%r13
0x00007fffe1006d17: movabs $0x7ffff73b7ca0,%r10
0x00007fffe1006d21: jmpq *(%r10,%rbx,8)
如上的汇编代码也是执行的退栈操作,最次要的就是把在调用解释执行办法时压入的实参从栈中弹出,接着就是执行 main()办法中 invokevirtual 中的下一条指令 pop。此时的栈状态如下图所示。
须要留神的是,此时的栈顶缓存中存储着调用 add()办法的执行后果,那么在跳转到下一条指令 pop 时,必须要从 pop 的 iep 入口进入,这样就能正确的执行上来了。
5. 退出 main()办法
当执行 pop 指令时,会从 iep 入口进入,执行的汇编代码如下:
// iep
push %rax
// ...
add $0x8,%rsp
因为 main()办法调用 add()办法不须要返回后果,所以对于 main()办法来说,这个后果会从 main()办法的表达式栈中弹出。上面接着执行 return 指令,这个指令对应的汇编代码如下:
// 将 JavaThread::do_not_unlock_if_synchronized 属性存储到 %dl 中
0x00007fffe101b770: mov 0x2ad(%r15),%dl
// 重置 JavaThread::do_not_unlock_if_synchronized 属性值为 false
0x00007fffe101b777: movb $0x0,0x2ad(%r15)
// 将 Method* 加载到 %rbx 中
0x00007fffe101b77f: mov -0x18(%rbp),%rbx
// 将 Method::_access_flags 加载到 %ecx 中
0x00007fffe101b783: mov 0x28(%rbx),%ecx
// 查看 Method::flags 是否蕴含 JVM_ACC_SYNCHRONIZED
0x00007fffe101b786: test $0x20,%ecx
// 如果办法不是同步办法,跳转到 ----unlocked----
0x00007fffe101b78c: je 0x00007fffe101b970
main()办法为非同步办法,所以跳转到 unlocked,在 unlocked 逻辑中会执行一些开释锁的逻辑,对于咱们本实例来说这不重要,咱们间接看退栈的操作,如下:
// 将 -0x8(%rbp)处保留的 old stack pointer(saved rsp)取出来放到 %rbx 中
0x00007fffe101bac7: mov -0x8(%rbp),%rbx
// 移除栈帧
// leave 指令相当于:// mov %rbp, %rsp
// pop %rbp
0x00007fffe101bacb: leaveq
// 将返回地址弹出到 %r13 中
0x00007fffe101bacc: pop %r13
// 设置 %rsp 为调用者的栈顶值
0x00007fffe101bace: mov %rbx,%rsp
0x00007fffe101bad1: jmpq *%r13
最初的栈状态如下图所示。
其中的 return address 是 C ++ 语言的返回地址,接下来如何退出如上的一些栈帧及完结办法就是 C ++ 的事儿了。