方法调用10——直接生成invokedynamic指令的方法

Java暂不支持直接生成invokedynamic指令,需要借助asm工具。

1.invokedynamic指令简介

invokedynamic #8,  0              // InvokeDynamic #0:approach:(Ljava/lang/String;)Lcom/enjoy/learn/core/lambdastream/test/test3/Strategy;

invokedynamic指令后面的信息是CONSTANT_InvokeDynamic_info

CONSTANT_InvokeDynamic_info {
    u1 tag;
    u2 bootstrap_method_attr_index;
    u2 name_and_type_index;
}
#8 = InvokeDynamic      #0:#64        // #0:approach:(Ljava/lang/String;)Lcom/enjoy/learn/core/lambdastream/test/test3/Strategy;

其中#64是方法名字和类型:

#64 = NameAndType        #83:#89       // approach:(Ljava/lang/String;)Lcom/enjoy/learn/core/lambdastream/test/test3/Strategy;

另外#0表示bootstrap_method:

BootstrapMethods:
  0: #61 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #62 (Ljava/lang/String;)Ljava/lang/String;
      #63 REF_invokeStatic com/enjoy/learn/core/lambdastream/test/test3/Strategize.lambda$test$0:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
      #62 (Ljava/lang/String;)Ljava/lang/String;

invokedynamic将CallSite抽象成一个Java类,并且将由JVM控制的方法调用和方法链接暴露给应用程序。

invokedynamic会捆绑一个CallSite,调用该CallSite所链接的MethodHandle。

在第一次执行invokedynamic时,JVM会调用其对应的Boostrap方法,来生成CallSite,并且进行绑定。之后,JVM会直接调用绑定的CallSite所链接的MethodHandle。

    public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String invokedName,
                                       MethodType invokedType,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType)
            throws LambdaConversionException {

该静态方法接收三个固定参数:
1)一个Lookup实例
2)目标方法名称字符串
3)MethodHandle类型MethodType(目标类型,根据实际参数生成的类型)

2.实验目的

生成一个方法,其通过invokedynamic指令调用任意类型对象的同一个方法(也就是上面的NameAndType)。

也即:
1)invokedynamic指定方法名和方法类型
2)传入对象,只要该对象具有该方法,即可进行调用

3.借用ASM添加invokedymic指令

需要在startRace中添加invokedynamic指令:

import java.lang.invoke.*;

class Horse {
  public void race() {
    System.out.println("Horse.race()"); 
  }
}

class Deer {
  public void race() {
    System.out.println("Deer.race()");
  }
}

// javac Circuit.java
// java Circuit
public class Circuit {

  public static void startRace(Object obj) {
    // aload obj
    // invokedynamic race()
  }

  public static void main(String[] args) {
    startRace(new Horse());
    // startRace(new Deer());
  }
  
  public static CallSite bootstrap(MethodHandles.Lookup l, String name, MethodType callSiteType) throws Throwable {
    MethodHandle mh = l.findVirtual(Horse.class, name, MethodType.methodType(void.class));
    return new ConstantCallSite(mh.asType(callSiteType));
  }
}

如下是修改Circuit.class的类ASMHelper:

import java.io.IOException;
import java.lang.invoke.*;
import java.nio.file.*;

import org.objectweb.asm.*;

public class ASMHelper implements Opcodes {

  private static class MyMethodVisitor extends MethodVisitor {

    private static final String BOOTSTRAP_CLASS_NAME = Circuit.class.getName().replace('.', '/');
    private static final String BOOTSTRAP_METHOD_NAME = "bootstrap";
    private static final String BOOTSTRAP_METHOD_DESC = MethodType
        .methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class)
        .toMethodDescriptorString();

    private static final String TARGET_METHOD_NAME = "race";
    private static final String TARGET_METHOD_DESC = "(Ljava/lang/Object;)V";

    public final MethodVisitor mv;

    public MyMethodVisitor(int api, MethodVisitor mv) {
      super(api);
      this.mv = mv;
    }

    @Override
    public void visitCode() {
      mv.visitCode();
      mv.visitVarInsn(ALOAD, 0);
      Handle h = new Handle(H_INVOKESTATIC, BOOTSTRAP_CLASS_NAME, BOOTSTRAP_METHOD_NAME, BOOTSTRAP_METHOD_DESC, false);
      mv.visitInvokeDynamicInsn(TARGET_METHOD_NAME, TARGET_METHOD_DESC, h);
      mv.visitInsn(RETURN);
      mv.visitMaxs(1, 1);
      mv.visitEnd();
    }
  }

  public static void main(String[] args) throws IOException {
    ClassReader cr = new ClassReader("Circuit");
    ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
    ClassVisitor cv = new ClassVisitor(ASM6, cw) {
      @Override
      public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
          String[] exceptions) {
        MethodVisitor visitor = super.visitMethod(access, name, descriptor, signature, exceptions);
        if ("startRace".equals(name)) {
          return new MyMethodVisitor(ASM6, visitor);
        }
        return visitor;
      }
    };
    cr.accept(cv, ClassReader.SKIP_FRAMES);

    Files.write(Paths.get("Circuit.class"), cw.toByteArray());
  }
}

获取两个jar包:
asm-6.0_BETA.jar
org.objectweb.asm-5.0.1.v201404251740.jar

执行操作:
javac Circuit.java
javac -cp ./asm-6.0_BETA.jar:. ASMHelper.java
java -cp ./asm-6.0_BETA.jar:. ASMHelper
java Circuit

结果:

Horse.race()

ASMHelper.java的作用:拼接出invokedynamic的bootstrap和NameAndType
1)增加了aload和invokedynamic指令
2)增加了invokedynamic指令需要的BootstrapMethods属性
3)以及相关的常量池中的项(#75 = InvokeDynamic等)

#75 = InvokeDynamic      #0:#74         // #0:race:(Ljava/lang/Object;)V

  public static void startRace(java.lang.Object);
    descriptor: (Ljava/lang/Object;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokedynamic #75,  0             // InvokeDynamic #0:race:(Ljava/lang/Object;)V
         6: return


BootstrapMethods:
  0: #72 invokestatic Circuit.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
SourceFile: "Circuit.java"
InnerClasses:
     public static final #28= #27 of #53; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles

但该方法有个缺陷:
MethodHandle mh = l.findVirtual(Horse.class, name, MethodType.methodType(void.class));
mh绑死在Horse.class,若main()中调用startRace(new Deer())会出错。

4.支持调用任意类的race方法

import java.lang.invoke.*;

public class MonomorphicInlineCache {

  private final MethodHandles.Lookup lookup;
  private final String name;

  public MonomorphicInlineCache(MethodHandles.Lookup lookup, String name) {
    this.lookup = lookup;
    this.name = name;
  }

  private Class<?> cachedClass = null;
  private MethodHandle mh = null;

  public void invoke(Object receiver) throws Throwable {
    if (cachedClass != receiver.getClass()) {
      cachedClass = receiver.getClass();
      mh = lookup.findVirtual(cachedClass, name, MethodType.methodType(void.class));
    }
    mh.invoke(receiver);
  }
import java.lang.invoke.*;

class Horse {
  public void race() {
    System.out.println("Horse.race()"); 
  }
}

class Deer {
  public void race() {
    System.out.println("Deer.race()");
  }
}

// javac Circuit.java
// java Circuit
public class Circuit {

  public static void startRace(Object obj) {
    // aload obj
    // invokedynamic race()
  }

  public static void main(String[] args) {
     startRace(new Horse());
     startRace(new Deer());
  }
  
  public static CallSite bootstrap(MethodHandles.Lookup l, String name, MethodType callSiteType) throws Throwable {
    MonomorphicInlineCache ic = new MonomorphicInlineCache(l, name);
    MethodHandle mh = l.findVirtual(MonomorphicInlineCache.class, "invoke", MethodType.methodType(void.class, Object.class));
    return new ConstantCallSite(mh.bindTo(ic));
  }
}

这里将启动方法绑定到MonomorphicInlineCache的invoke方法,一般情况下,invoke会根据对象实例的类型生成MethodHandle,然后进行调用。如果之前缓存了,则直接使用缓存的MethodHandle进行调用。因此可以实现任意类调用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Invokedynamic指令是java7中加入的字节码指令,理解这条指令可以让我们熟悉程序的执行流程,这篇文章将...
    请输入妮称阅读 8,452评论 0 1
  • 概述 执行引擎是Java虚拟机最核心的组成部分之一。“虚拟机”是一个相对于“物理机”的概念,这两个机器都有代码执行...
    胡二囧阅读 4,429评论 2 2
  • 前言 很早之前就写过面向切面的编程思想,主要学习了AOP的思想(参考:AOP简介)以及使用 AspectJ 实现简...
    Whyn阅读 13,742评论 4 40
  • 方法调用不是方法执行,方法调用是让jvm确定调用哪个方法,所以,程序运行时的它是最普遍、最频繁的操作。jvm需要在...
    FantJ阅读 3,449评论 0 2
  • 悠悠思绪 浸染着秋天的童话 绵长了半个村庄 我走进这浓墨重彩的田野, 等待 一只大雁的讯息 看看 小野菊披着洁白的...
    空壳的小蜗牛阅读 1,418评论 0 0

友情链接更多精彩内容