当前位置:  开发笔记 > 编程语言 > 正文

从堆转储中的错位名称中查找Java lambda

如何解决《从堆转储中的错位名称中查找Javalambda》经验,为你挑选了1个好方法。

我正在寻找内存泄漏,堆转储显示我有许多lambda实例正在保存有问题的对象.lambda的名称是最后的周围类名$$lambda$107.我还可以看到它有一个字段(它是正确的名称),调用arg$1它引用填充堆的对象.不幸的是,我在这堂课中有很多lambdas,我想知道我能做些什么来缩小范围.

我假设arg$1是一个隐式参数 - lambda表达式中的一个自由变量,当lambda成为一个闭包时被捕获.那是对的吗?

我也猜测107在孤立中没有真正的帮助,但是我可以设置一些标志来记录哪个lambda表达式得到什么数字?

还有其他有用的提示?



1> Stuart Marks..:

OP的猜想是正确的,它arg$1是包含捕获值的lambda对象的字段.lukeg的答案是在正确的轨道上,让lambda metafactory转储其代理类.(1)

这是一种使用该javap工具跟踪将引用保存回源代码的实例的方法.基本上你找到合适的代理类; 反汇编它以找出它所调用的合成lambda方法; 然后将该合成lambda方法与源代码中的特定lambda表达式相关联.

(大多数,如果不是所有这些信息,都适用于Oracle JDK和OpenJDK.它可能不适用于不同的JDK实现.此外,这可能会在未来发生变化.这应该适用于任何最新的Oracle JDK 8或OpenJDK 8,虽然.它可能会继续在JDK 9中工作.)

首先,有点背景.编译包含lambdas的源文件时,javac会将lambda主体编译为驻留在包含类中的合成方法.这些方法是私有的和静态的,它们的名称类似于lambda$$where 方法是包含lambda的方法的名称,count是一个顺序计数器,它对源文件开头的方法进行编号(从零开始).

在运行时首次计算 lambda表达式时,将调用lambda metafactory.这产生了一个实现lambda功能接口的类.它实例化该类,将参数带入功能接口方法(如果有的话),将它们与任何捕获的值组合,并调用javac如上所述编译的合成方法.该实例被称为"功能对象"或"代理".

通过获取lambda metafactory来转储其代理类,您可以使用javap反汇编字节码并将代理实例跟踪回生成它的lambda表达式.这可能是一个例子最好的例子.请考虑以下代码:

public class CaptureTest {
    static List list;

    static IntSupplier foo(boolean b, Object o) {
        if (b) {
            return () -> 0;                      // line 20
        } else {
            int h = o.hashCode();
            return () -> h;                      // line 23
        }
    }

    static IntSupplier bar(boolean b, Object o) {
        if (b) {
            return () -> o.hashCode();           // line 29
        } else {
            int len = o.toString().length();
            return () -> len;                    // line 32
        }
    }

    static void run() {
        Object big = new byte[10_000_000];

        list = Arrays.asList(
            bar(false, big),
            bar(true,  big),
            foo(false, big),
            foo(true,  big));

        System.out.println("Done.");
    }

    public static void main(String[] args) throws InterruptedException {
        run();
        Thread.sleep(Long.MAX_VALUE); // stay alive so a heap dump can be taken
    }
}

此代码分配一个大型数组,然后计算四个不同的lambda表达式.其中一个捕获对大型数组的引用.(你可以通过检查来判断你是否知道你在寻找什么,但有时这很难.)哪个lambda正在捕捉?

首先要做的是编译这个类并运行javap -v -p CaptureTest.该-v选项显示反汇编的字节码和其他信息,如行号表.-p必须提供该选项才能javap反汇编私有方法.这个输出包括很多东西,但重要的部分是合成的lambda方法:

private static int lambda$bar$3(int);
  descriptor: (I)I
  flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
  Code:
    stack=1, locals=1, args_size=1
       0: iload_0
       1: ireturn
    LineNumberTable:
      line 32: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       2     0   len   I

private static int lambda$bar$2(java.lang.Object);
  descriptor: (Ljava/lang/Object;)I
  flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
  Code:
    stack=1, locals=1, args_size=1
       0: aload_0
       1: invokevirtual #3                  // Method java/lang/Object.hashCode:()I
       4: ireturn
    LineNumberTable:
      line 29: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       5     0     o   Ljava/lang/Object;

private static int lambda$foo$1(int);
  descriptor: (I)I
  flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
  Code:
    stack=1, locals=1, args_size=1
       0: iload_0
       1: ireturn
    LineNumberTable:
      line 23: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       2     0     h   I

private static int lambda$foo$0();
  descriptor: ()I
  flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
  Code:
    stack=1, locals=0, args_size=0
       0: iconst_0
       1: ireturn
    LineNumberTable:
      line 20: 0

方法名称末尾的计数器从零开始,并从文件的开头按顺序编号.此外,合成方法名称包含包含lambda表达式的方法的名称,因此我们可以告诉从单个方法中出现的几个lambda中生成的每个方法.

然后,内存分析器下运行的程序,供给命令行参数-Djdk.internal.lambda.dumpProxyClasses=java命令.这会导致lambda metafactory将其生成的类转储到指定的目录(必须已存在).

获取应用程序的内存配置文件并进行检查.有很多种方法可以做到这一点; 我使用了NetBeans内存分析器.当我运行它时,它告诉我一个带有10,000,000个元素的byte []由一个arg$1名为的类中的字段保存CaptureTest$$Lambda$9.这就是OP所得到的.

此类名称上的计数器没有用,因为它表示由lambda metafactory生成的类的序列号,按照它们在运行时生成的顺序.知道运行时序列并没有告诉我们它在源代码中的起源.

但是,我们已经要求lambda metafactory转储它的类,所以我们可以去看一下这个特定的类来看它的作用.实际上,在输出目录中,有一个文件CaptureTest$$Lambda$9.class.javap -c在它上面运行显示以下内容:

final class CaptureTest$$Lambda$9 implements java.util.function.IntSupplier {
  public int getAsInt();
    Code:
       0: aload_0
       1: getfield      #15                 // Field arg$1:Ljava/lang/Object;
       4: invokestatic  #28                 // Method CaptureTest.lambda$bar$2:(Ljava/lang/Object;)I
       7: ireturn
}

您可以反编译常量池条目,但javap有助于将符号名称放在字节码右侧的注释中.您可以看到这会加载arg$1字段 - 违规引用 - 并将其传递给方法CaptureTest.lambda$bar$2.这是源文件中的lambda number 2(从零开始),它是bar()方法中两个lambda表达式中的第一个.现在,您可以返回到javap原始类的输出,并使用lambda静态方法中的行号信息来查找源文件中的位置.该CaptureTest.lambda$bar$2方法的行号信息指向第29行.此位置的lambda是

    () -> o.hashCode()

where o是一个自由变量,它是bar()方法的一个参数的捕获.

推荐阅读
携手相约幸福
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有