CoreLibs - invokedynamic字节码指北
VM层
templateInterpreter的invokedynamic如下:
void TemplateTable::invokedynamic(int byte_no) {
transition(vtos, vtos);
assert(byte_no == f1_byte, "use this argument");
const Register rbx_method = rbx;
const Register rax_callsite = rax;
prepare_invoke(byte_no, rbx_method, rax_callsite);
// rax: CallSite object (from cpool->resolved_references[f1])
// rbx: MH.linkToCallSite method (from f2)
// Note: rax_callsite is already pushed by prepare_invoke
// %%% should make a type profile for any invokedynamic that takes a ref argument
// profile this call
__ profile_call(rbcp);
__ profile_arguments_type(rdx, rbx_method, rbcp, false);
__ verify_oop(rax_callsite);
__ jump_from_interpreted(rbx_method, rdx);
}
void TemplateTable::prepare_invoke(int byte_no,
Register method, // linked method (or i-klass)
Register index, // itable index, MethodType, etc.
Register recv, // if caller wants to see it
Register flags // if caller wants to test it
) {
// determine flags
const Bytecodes::Code code = bytecode();
const bool is_invokeinterface = code == Bytecodes::_invokeinterface;
const bool is_invokedynamic = code == Bytecodes::_invokedynamic;
const bool is_invokehandle = code == Bytecodes::_invokehandle;
const bool is_invokevirtual = code == Bytecodes::_invokevirtual;
const bool is_invokespecial = code == Bytecodes::_invokespecial;
const bool load_receiver = (recv != noreg);
const bool save_flags = (flags != noreg);
assert(load_receiver == (code != Bytecodes::_invokestatic && code != Bytecodes::_invokedynamic), "");
assert(save_flags == (is_invokeinterface || is_invokevirtual), "need flags for vfinal");
assert(flags == noreg || flags == rdx, "");
assert(recv == noreg || recv == rcx, "");
// setup registers & access constant pool cache
if (recv == noreg) recv = rcx;
if (flags == noreg) flags = rdx;
assert_different_registers(method, index, recv, flags);
// save 'interpreter return address'
__ save_bcp();
load_invoke_cp_cache_entry(byte_no, method, index, flags, is_invokevirtual, false, is_invokedynamic);
// maybe push appendix to arguments (just before return address)
if (is_invokedynamic || is_invokehandle) {
Label L_no_push;
__ testl(flags, (1 << ConstantPoolCacheEntry::has_appendix_shift));
__ jcc(Assembler::zero, L_no_push);
// Push the appendix as a trailing parameter.
// This must be done before we get the receiver,
// since the parameter_size includes it.
__ push(rbx);
__ mov(rbx, index);
assert(ConstantPoolCacheEntry::_indy_resolved_references_appendix_offset == 0, "appendix expected at index+0");
__ load_resolved_reference_at_index(index, rbx);
__ pop(rbx);
__ push(index); // push appendix (MethodType, CallSite, etc.)
__ bind(L_no_push);
}
// load receiver if needed (after appendix is pushed so parameter size is correct)
// Note: no return address pushed yet
if (load_receiver) {
__ movl(recv, flags);
__ andl(recv, ConstantPoolCacheEntry::parameter_size_mask);
const int no_return_pc_pushed_yet = -1; // argument slot correction before we push return address
const int receiver_is_at_end = -1; // back off one slot to get receiver
Address recv_addr = __ argument_address(recv, no_return_pc_pushed_yet + receiver_is_at_end);
__ movptr(recv, recv_addr);
__ verify_oop(recv);
}
if (save_flags) {
__ movl(rbcp, flags);
}
// push return address
__ push(flags);
// Restore flags value from the constant pool cache, and restore rsi
// for later null checks. r13 is the bytecode pointer
if (save_flags) {
__ movl(flags, rbcp);
__ restore_bcp();
}
// compute return type
__ shrl(flags, ConstantPoolCacheEntry::tos_state_shift);
// Make sure we don't need to mask flags after the above shift
ConstantPoolCacheEntry::verify_tos_state_shift();
// load return address
{
const address table_addr = (address) Interpreter::invoke_return_entry_table_for(code);
ExternalAddress table(table_addr);
LP64_ONLY(__ lea(rscratch1, table));
LP64_ONLY(__ movptr(flags, Address(rscratch1, flags, Address::times_ptr)));
NOT_LP64(__ movptr(flags, ArrayAddress(table, Address(noreg, flags, Address::times_ptr))));
}
}
void TemplateTable::load_invoke_cp_cache_entry(int byte_no,
Register method,
Register itable_index,
Register flags,
bool is_invokevirtual,
bool is_invokevfinal, /*unused*/
bool is_invokedynamic) {
// setup registers
const Register cache = rcx;
const Register index = rdx;
assert_different_registers(method, flags);
assert_different_registers(method, cache, index);
assert_different_registers(itable_index, flags);
assert_different_registers(itable_index, cache, index);
// determine constant pool cache field offsets
assert(is_invokevirtual == (byte_no == f2_byte), "is_invokevirtual flag redundant");
const int method_offset = in_bytes(
ConstantPoolCache::base_offset() +
((byte_no == f2_byte)
? ConstantPoolCacheEntry::f2_offset()
: ConstantPoolCacheEntry::f1_offset()));
const int flags_offset = in_bytes(ConstantPoolCache::base_offset() +
ConstantPoolCacheEntry::flags_offset());
// access constant pool cache fields
const int index_offset = in_bytes(ConstantPoolCache::base_offset() +
ConstantPoolCacheEntry::f2_offset());
size_t index_size = (is_invokedynamic ? sizeof(u4) : sizeof(u2));
resolve_cache_and_index(byte_no, cache, index, index_size);
__ movptr(method, Address(cache, index, Address::times_ptr, method_offset));
if (itable_index != noreg) {
// pick up itable or appendix index from f2 also:
__ movptr(itable_index, Address(cache, index, Address::times_ptr, index_offset));
}
__ movl(flags, Address(cache, index, Address::times_ptr, flags_offset));
}
void TemplateTable::resolve_cache_and_index(int byte_no,
Register Rcache,
Register index,
size_t index_size) {
const Register temp = rbx;
assert_different_registers(Rcache, index, temp);
Label resolved;
Bytecodes::Code code = bytecode();
switch (code) {
case Bytecodes::_nofast_getfield: code = Bytecodes::_getfield; break;
case Bytecodes::_nofast_putfield: code = Bytecodes::_putfield; break;
default: break;
}
assert(byte_no == f1_byte || byte_no == f2_byte, "byte_no out of range");
__ get_cache_and_index_and_bytecode_at_bcp(Rcache, index, temp, byte_no, 1, index_size);
__ cmpl(temp, code); // have we resolved this bytecode?
__ jcc(Assembler::equal, resolved);
// resolve first time through
address entry = CAST_FROM_FN_PTR(address, InterpreterRuntime::resolve_from_cache);
__ movl(temp, code);
__ call_VM(noreg, entry, temp);
// Update registers with resolved info
__ get_cache_and_index_at_bcp(Rcache, index, 1, index_size);
__ bind(resolved);
}
void TemplateTable::resolve_cache_and_index(int byte_no,
Register Rcache,
Register index,
size_t index_size) {
const Register temp = rbx;
assert_different_registers(Rcache, index, temp);
Label resolved;
Bytecodes::Code code = bytecode();
switch (code) {
case Bytecodes::_nofast_getfield: code = Bytecodes::_getfield; break;
case Bytecodes::_nofast_putfield: code = Bytecodes::_putfield; break;
default: break;
}
assert(byte_no == f1_byte || byte_no == f2_byte, "byte_no out of range");
__ get_cache_and_index_and_bytecode_at_bcp(Rcache, index, temp, byte_no, 1, index_size);
__ cmpl(temp, code); // have we resolved this bytecode?
__ jcc(Assembler::equal, resolved);
// resolve first time through
address entry = CAST_FROM_FN_PTR(address, InterpreterRuntime::resolve_from_cache);
__ movl(temp, code);
__ call_VM(noreg, entry, temp);
// Update registers with resolved info
__ get_cache_and_index_at_bcp(Rcache, index, 1, index_size);
__ bind(resolved);
}
TemplateTable::invokedynamic由两步组成,第一步prepare_invoke将要调用的方法放到rbx,然后jump_from_interpreted跳到这个rbx方法入口。所以invokedynamic的所有故事主要发生在prepare_invoke。
prepare_invoke一步步会调用到InterpreterRuntime::resolve_from_cache,注意,从prepare_invoke到resolve_from_cache都是模板解释器的JIT代码,到resolve_from_cache就是VM代码了。resolve_from_cache是几个invoke*方法共同的入口:
IRT_ENTRY(void, InterpreterRuntime::resolve_from_cache(JavaThread* thread, Bytecodes::Code bytecode)) {
switch (bytecode) {
case Bytecodes::_getstatic:
case Bytecodes::_putstatic:
case Bytecodes::_getfield:
case Bytecodes::_putfield:
resolve_get_put(thread, bytecode);
break;
case Bytecodes::_invokevirtual:
case Bytecodes::_invokespecial:
case Bytecodes::_invokestatic:
case Bytecodes::_invokeinterface:
resolve_invoke(thread, bytecode);
break;
case Bytecodes::_invokehandle:
resolve_invokehandle(thread);
break;
case Bytecodes::_invokedynamic:
resolve_invokedynamic(thread);
break;
default:
fatal("unexpected bytecode: %s", Bytecodes::name(bytecode));
break;
}
}
IRT_END
这里主要关注InterpreterRuntime::resolve_invokedynamic。它会一步步走下去,最终走到LinkResolver::resolve_invokedynamic:
void LinkResolver::resolve_invokedynamic(CallInfo& result, const constantPoolHandle& pool, int index, TRAPS) {
Symbol* method_name = pool->name_ref_at(index);
Symbol* method_signature = pool->signature_ref_at(index);
Klass* current_klass = pool->pool_holder();
// Resolve the bootstrap specifier (BSM + optional arguments).
Handle bootstrap_specifier;
// Check if CallSite has been bound already:
ConstantPoolCacheEntry* cpce = pool->invokedynamic_cp_cache_entry_at(index);
int pool_index = cpce->constant_pool_index();
if (cpce->is_f1_null()) {
if (cpce->indy_resolution_failed()) {
ConstantPool::throw_resolution_error(pool,
ResolutionErrorTable::encode_cpcache_index(index),
CHECK);
}
// The initial step in Call Site Specifier Resolution is to resolve the symbolic
// reference to a method handle which will be the bootstrap method for a dynamic
// call site. If resolution for the java.lang.invoke.MethodHandle for the bootstrap
// method fails, then a MethodHandleInError is stored at the corresponding bootstrap
// method's CP index for the CONSTANT_MethodHandle_info. So, there is no need to
// set the indy_rf flag since any subsequent invokedynamic instruction which shares
// this bootstrap method will encounter the resolution of MethodHandleInError.
oop bsm_info = pool->resolve_bootstrap_specifier_at(pool_index, THREAD);
Exceptions::wrap_dynamic_exception(CHECK);
assert(bsm_info != NULL, "");
// FIXME: Cache this once per BootstrapMethods entry, not once per CONSTANT_InvokeDynamic.
bootstrap_specifier = Handle(THREAD, bsm_info);
}
if (!cpce->is_f1_null()) {
methodHandle method( THREAD, cpce->f1_as_method());
Handle appendix( THREAD, cpce->appendix_if_resolved(pool));
Handle method_type(THREAD, cpce->method_type_if_resolved(pool));
result.set_handle(method, appendix, method_type, THREAD);
Exceptions::wrap_dynamic_exception(CHECK);
return;
}
if (TraceMethodHandles) {
ResourceMark rm(THREAD);
tty->print_cr("resolve_invokedynamic #%d %s %s in %s",
ConstantPool::decode_invokedynamic_index(index),
method_name->as_C_string(), method_signature->as_C_string(),
current_klass->name()->as_C_string());
tty->print(" BSM info: "); bootstrap_specifier->print();
}
resolve_dynamic_call(result, pool_index, bootstrap_specifier, method_name,
method_signature, current_klass, THREAD);
if (HAS_PENDING_EXCEPTION && PENDING_EXCEPTION->is_a(SystemDictionary::LinkageError_klass())) {
int encoded_index = ResolutionErrorTable::encode_cpcache_index(index);
bool recorded_res_status = cpce->save_and_throw_indy_exc(pool, pool_index,
encoded_index,
pool()->tag_at(pool_index),
CHECK);
if (!recorded_res_status) {
// Another thread got here just before we did. So, either use the method
// that it resolved or throw the LinkageError exception that it threw.
if (!cpce->is_f1_null()) {
methodHandle method( THREAD, cpce->f1_as_method());
Handle appendix( THREAD, cpce->appendix_if_resolved(pool));
Handle method_type(THREAD, cpce->method_type_if_resolved(pool));
result.set_handle(method, appendix, method_type, THREAD);
Exceptions::wrap_dynamic_exception(CHECK);
} else {
assert(cpce->indy_resolution_failed(), "Resolution failure flag not set");
ConstantPool::throw_resolution_error(pool, encoded_index, CHECK);
}
return;
}
assert(cpce->indy_resolution_failed(), "Resolution failure flag wasn't set");
}
}
LinkResolver::resolve_invokedynamic用pool->resolve_bootstrap_specifier_at找到bsm(BootStrap Method)方法,然后resolve_dynamic_call根据bsm方法找到调用方法。其中找bsm会call到Java代码MethodHandleNatives.linkMethodHandleConstant,调用bsm也会call到Java代码的MethodHandleNatives.linkCallSite。
换句话说,prepare_invoke最终调用MethodHandleNatives.linkMethodHandleConstant找bsm方法,然后调用MethodHandleNatives.linkCallSite根据bsm方法找到最终调用的方法,然后jump_from_interpreted调用这个方法。
Java层
java层就好很多了,假设我们的代码是
public class MHTest {
public static void main(String[] args) {
Runnable r = ()->{
System.out.println("fk");
};
r.run();
}
}
脑补一下,jvm走到lambda处会调用模板解释器解释执行invokedynamic,这一步对应TemplateTable::invokedynamic。,然后前面提到这个方法先用linkMethodHandleConstant找bsm方法:
然后它返回一个bsm方法(MethodHandle),传递给linkCallSite作为bootstrapMethodObj,根据它找到要调用的方法:
与例子结合,lambda的bsm方法是LambdaMetafactory.metafacotry,linkCallSite会调用metafactory生成callsite:
然后返回metafactory找到的方法,由解释器调用它。再精简一点,invokedynamic本质上是增加了一个bsm方法作为中间层,用户可以写自己的bsm方法逻辑来确定调用哪个方法,以前的查找调用方法逻辑都是写死在虚拟机中,现在多了一个中间层。
LambdaMetafactory
public static CallSite metafactory(MethodHandles.Lookup caller,
String invokedName,
MethodType invokedType,
MethodType samMethodType,
MethodHandle implMethod,
MethodType instantiatedMethodType)
throws LambdaConversionException {
AbstractValidatingLambdaMetafactory mf;
mf = new InnerClassLambdaMetafactory(caller, invokedType,
invokedName, samMethodType,
implMethod, instantiatedMethodType,
false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
mf.validateMetafactoryArgs();
return mf.buildCallSite();
}
实际上它调用的是InnerClassLambdaMetafactory.buildCallSite:
CallSite buildCallSite() throws LambdaConversionException {
// 1. 创建生成的类对象
final Class<?> innerClass = spinInnerClass();
if (invokedType.parameterCount() == 0) {
// 2. 用反射获取构造函数
final Constructor<?>[] ctrs = AccessController.doPrivileged(
new PrivilegedAction[]>() {
@Override
public Constructor<?>[] run() {
Constructor<?>[] ctrs = innerClass.getDeclaredConstructors();
if (ctrs.length == 1) {
// The lambda implementing inner class constructor is private, set
// it accessible (by us) before creating the constant sole instance
ctrs[0].setAccessible(true);
}
return ctrs;
}
});
if (ctrs.length != 1) {
throw new LambdaConversionException("Expected one lambda constructor for "
+ innerClass.getCanonicalName() + ", got " + ctrs.length);
}
try {
// 3. 创建实例
Object inst = ctrs[0].newInstance();
// 4. 根据实例和samBase(接口类型)生成MethodHandle
// 5. 生成ConstantCallSite
return new ConstantCallSite(MethodHandles.constant(samBase, inst));
}
catch (ReflectiveOperationException e) {
throw new LambdaConversionException("Exception instantiating lambda object", e);
}
} else {
try {
UNSAFE.ensureClassInitialized(innerClass);
return new ConstantCallSite(
MethodHandles.Lookup.IMPL_LOOKUP
.findStatic(innerClass, NAME_FACTORY, invokedType));
}
catch (ReflectiveOperationException e) {
throw new LambdaConversionException("Exception finding constructor", e);
}
}
}
它生成一个.class文件,虚拟机默认不会输出,需要下面设置VM option-Djdk.internal.lambda.dumpProxyClasses=.
,Dump出虚拟机生成的类我得到的是:
import java.lang.invoke.LambdaForm.Hidden;
// $FF: synthetic class
final class MethodReference$$Lambda$1 implements Encode {
private MethodReference$$Lambda$1() {
}
@Hidden
public void encode(Derive var1) {
((Base)var1).encrypt();
}
}
该类实现了传来的接口函数(动态类生成,spring警告)。回到buildCallSite()源码,它使用MethodHandles.constant(samBase, inst)
创建MethdHandle,放到CallSite里面,完成整个LambdaMetafactory的工作。 MethodHandles.constant(samBase, inst)
相当于一个总是返回inst的方法。
总结是最重要的,说到底,invokedynamic的思想是增加一层的思想,计算机科学中的银弹。它通过BSM第一次执行时生成callsite,后面调用callsite,而正常流程是直接调用callsite。