Java8学习笔记(3) -- InvokeDynamic指令在Groovy里的使用

上一篇文章以Java8的Lambda表达式为切入点,讨论了invokedynamic(下文简称indy)指令。为了加深对indy指令的理解,本文来研究一下Groovy是如何利用indy指令的。

准备工作

虽然Groovy从2.0开始支持indy,但并非是默认启用indy的,而是需要选择适当的jar(带indy后缀),和命令行参数(-indy)。下载Groovy2.3,为了方便起见,我们使用里面的groovy-all-2.3.0-indy.jar:

创建一个文件夹,把groovy-all-2.3.0-indy.jar提取到里面。然后再在文件夹里创建一个groovy类,名为InDyTest.groovy,内容如下:

class InDyTest {
    
    def m(x, y) {
        x + y
    }
    
}
打开命令行窗口,cd到这个文件夹下,用下面的命令编译InDyTest.groovy:

java -cp groovy-all-2.3.0-indy.jar org.codehaus.groovy.tools.FileSystemCompiler -indy InDyTest.groovy
如果Java环境正确安装的话,应该能编译出InDyTest.class,然后用下面的命令反编译它:

javap -v -p InDyTest.class > InDyTest_javap.txt
到此为止,你创建的文件夹下面应该包含四个文件,如下图所示:

观察class文件

用文本编辑器打开InDyTest_javap.txt,找到m()方法,可以看到,groovyc确实为x+y这句代码(实际上是x.plus(y))生成了一条indy指令:

找到常量池#43:

根据上一篇文章的介绍,知道它表示的是像下面这样一个动态调用:

Object invoke(Object, Object) {...}
再看BootstrapMethod#0:

于是我们知道启动方法是IndyInterface.bootstrap()。

bootstrap()方法

下面是IndyInterface.bootstrap()方法的代码:

/**
* bootstrap method for method calls from Groovy compiled code with indy
* enabled. This method gets a flags parameter which uses the following
* encoding:<ul>
* <li>{@value #SAFE_NAVIGATION} is the flag value for safe navigation see {@link #SAFE_NAVIGATION}<li/>
* <li>{@value #THIS_CALL} is the flag value for a call on this see {@link #THIS_CALL}</li>
* </ul>
* @param caller - the caller
* @param callType - the type of the call
* @param type - the call site type
* @param name - the real method name
* @param flags - call flags
* @return the produced CallSite
* @since Groovy 2.1.0
*/
public static CallSite bootstrap(Lookup caller, String callType, MethodType type, String name, int flags) {
    boolean safe = (flags&SAFE_NAVIGATION)!=0;
    boolean thisCall = (flags&THIS_CALL)!=0;
    boolean spreadCall = (flags&SPREAD_CALL)!=0;
    int callID;
    if (callType.equals(CALL_TYPES.METHOD.getCallSiteName())) {
        callID = CALL_TYPES.METHOD.ordinal();
    } else if (callType.equals(CALL_TYPES.INIT.getCallSiteName())) {
        callID = CALL_TYPES.INIT.ordinal();
    } else if (callType.equals(CALL_TYPES.GET.getCallSiteName())) {
        callID = CALL_TYPES.GET.ordinal();
    } else if (callType.equals(CALL_TYPES.SET.getCallSiteName())) {
        callID = CALL_TYPES.SET.ordinal();
    } else if (callType.equals(CALL_TYPES.CAST.getCallSiteName())) {
        callID = CALL_TYPES.CAST.ordinal();
    } else {
        throw new GroovyBugError("Unknown call type: "+callType);
    }
    return realBootstrap(caller, name, callID, type, safe, thisCall, spreadCall);
}
根据class文件分析可知,当JVM调用bootstrap()方法时,传入的callType是"invoke"(==CALL_TYPES.METHOD.getCallSiteName()),name是"plus",flags是0。于是上面的方法实际上等价于:

public static CallSite bootstrap(Lookup caller, String callType, MethodType type, String name, int flags) {
    return realBootstrap(caller, "plus", CALL_TYPES.METHOD, type, false, false, false);
}

realBootstrap()方法

/** 
* backing bootstrap method with all parameters
*/
private static CallSite realBootstrap(Lookup caller, String name, int callID, MethodType type, boolean safe, boolean thisCall, boolean spreadCall) {
    // since indy does not give us the runtime types
    // we produce first a dummy call site, which then changes the target to one,
    // that does the method selection including the the direct call to the
    // real method.
    MutableCallSite mc = new MutableCallSite(type);
    MethodHandle mh = makeFallBack(mc,caller.lookupClass(),name,callID,type,safe,thisCall,spreadCall);
    mc.setTarget(mh);
    return mc;
}
realBootstrap()方法比较复杂,我自己也没彻底搞清楚。初略的说,这个方法返回一个CallSite,这个CallSite的目标MethodHandle最终会调用selectMethod()方法,然后selectMethod()方法会根据caller等信息选出最合适的一个方法进行调用。
/**
* Core method for indy method selection using runtime types.
*/
public static Object selectMethod(MutableCallSite callSite, Class sender, String methodName, int callID, Boolean safeNavigation, Boolean thisCall, Boolean spreadCall, Object dummyReceiver, Object[] arguments) throws Throwable {
    Selector selector = Selector.getSelector(callSite, sender, methodName, callID, safeNavigation, thisCall, spreadCall, arguments);
    selector.setCallSiteTarget();

    MethodHandle call = selector.handle.asSpreader(Object[].class, arguments.length);
    call = call.asType(MethodType.methodType(Object.class,Object[].class));
    return call.invokeExact(arguments);
}

总结

Groovy为了利用indy指令,大量使用了MethodHandle。所以,要想彻底理解indy指令,还是先彻底理解MethodHandle(以及相关的)类吧 :(

参考资料

Using Groovy to play with invokedynamic




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章