【问题标题】:Can you inspect the byte code of a Java 8 lambda at runtime?你能在运行时检查 Java 8 lambda 的字节码吗?
【发布时间】:2017-02-27 03:54:25
【问题描述】:

如果你有一个像这样的匿名类

Predicate<String> isEmpty = new Predicate<String>() {
    public boolean test(String t) {
        return t.isEmpty();
    }
};

通过对isEmpty 的引用的库可以检查字节码以查看它的作用并可能对其进行操作。有没有办法为 lambdas 做到这一点?

Predicate<String> isEmpty = String::isEmpty;

例如说有这个代码和字节码

public class Main {
    public static void test(Predicate<String> tester) {
        System.out.println("tester.getClass()= " + tester.getClass());
        System.out.println("tester.getClass().getClassLoader()="+ tester.getClass().getClassLoader());
    }
    public static void main(String... args) {
        Predicate<String> isEmpty = String::isEmpty;
        test(isEmpty);
    }
}

$ javap -cp . -c -private Main.class
Compiled from "Main.java"
public class Main {
  public Main();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return        

  public static void test(java.util.function.Predicate<java.lang.String>);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: new           #3                  // class java/lang/StringBuilder
       6: dup           
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      10: ldc           #5                  // String tester.getClass()= 
      12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: aload_0       
      16: invokevirtual #7                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
      19: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
      22: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      25: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      28: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      31: new           #3                  // class java/lang/StringBuilder
      34: dup           
      35: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      38: ldc           #11                 // String tester.getClass().getClassLoader()=
      40: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      43: aload_0       
      44: invokevirtual #7                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
      47: invokevirtual #12                 // Method java/lang/Class.getClassLoader:()Ljava/lang/ClassLoader;
      50: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
      53: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      56: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      59: return        

  public static void main(java.lang.String...);
    Code:
       0: invokedynamic #13,  0             // InvokeDynamic #0:test:()Ljava/util/function/Predicate;
       5: astore_1      
       6: aload_1       
       7: invokestatic  #14                 // Method test:(Ljava/util/function/Predicate;)V
      10: return        
}

test 中引用tester 如何找到调用了哪个方法?

【问题讨论】:

  • 通过方法引用,您可以查看引用了哪个方法,然后查找该方法的字节码。对于常规的旧 lambda,lambda 字节码应位于 lambda$# 私有方法中。只需 javap-private 选项。
  • 您能澄清一下您要寻找的答案吗?字节码确实包含 lambdas 和方法引用的条目,所以,是的,您可以检查字节码。你问怎么做?
  • @SotiriosDelimanolis 是的。假设我有上面的例子。我参考了 isEmpty。如何找到此实例将调用的代码?
  • 看来 BootstrapMethods 可能掌握着关键。这是这种情况/最好的方法吗?
  • 可怜的赏金......不过,我很欣赏你的来宾;)

标签: java lambda java-8 bytecode


【解决方案1】:

如果您只想查看字节码:

javap -c -p -v classfile
      ^disassemble
         ^private methods
            ^verbose, including constant pool and bootstrap methods attribute

但是,如果您想在运行时尝试这样做,那么您就很不走运了(按照设计,我们没有像表达式树这样的东西),正如另一个答案所暗示的那样。

【讨论】:

  • +1 您知道更改选项jdk.internal.lambda.dumpProxyClasses 的任何计划吗?由于它是“内部的”,我可以期待它随时更改,但了解这可能会有所帮助。
  • 当然,Brian 是对的,没有提供公共 API 来做到这一点,但这并不意味着不可能,github.com/jhalterman/typetools 做了一些棘手的事情来加载常量池并找到引用的方法.执行此操作的代码有些脆弱,例如,我认为 u60 破坏了它。
【解决方案2】:

简单的答案是:你不能。关于这件事有一个related answer of Brian Goetz。为了实现 lambda 表达式,javac 创建了一个 INVOKEDYNAMIC 指令,它将调用委托给 LambdaMetafactory 的引导方法。对于 OpenJDK,此引导方法随后使用 ASM 在运行时创建所需接口的实现。

test 方法中,检索到的实例tester 属于这个ASM 生成的类,因此您无需读取类文件来找出Predicate 代表的方法。在一般情况下,如何将 lambda 表达式映射到接口实现的确切决定留给运行时环境,这会使您的问题变得更加困难。找出 lambda 表达式代表哪种方法的唯一方法是读取其创建的字节码,即解释 main 方法,例如。

【讨论】:

    【解决方案3】:

    毕竟,我找到了一种获取 lambda 表达式的类文件的方法,该文件相当可靠(但当然仍然依赖于实现细节。)对于实验性实现,我现在使用 Java agent,它允许类的重新转换,但它本身是作为无操作实现的,我只对作为参数传递的二进制数组感兴趣。

    在通过已注册的 Java 代理获得 Instrumentation 实例后,注册一个 ClassFileTransformer,然后在一个 重新转换 lambdaInstance.getClass() 时通过二进制表示形式通知它。这当然是一个相当老套的解决方案,一旦 lambdas 的内部实现在未来发生变化,它可能会中断。

    不幸的是,我没有找到任何关于代理在标准兼容 JVM 上对 lambda 表达式的合成类进行类重新转换时应该如何表现的文档。

    【讨论】:

    • AFAIK,缺少文档的问题已经解决——不再为 lambda 类调用类文件转换器……
    • 没错。这适用于由匿名类加载器定义的所有类。
    【解决方案4】:

    我找到了另一种方法:通过修补InnerClassLambdaMetafactory,可以将注释添加到指向实现方法的 lambda 类。这可以在运行时通过加载代理并重新转换元工厂来实现。详情请见my Blog

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-09
      • 2020-12-19
      相关资源
      最近更新 更多