gpt4 book ai didi

java - 从堆转储中的错误名称中查找 Java lambda

转载 作者:搜寻专家 更新时间:2023-10-30 21:08:09 24 4
gpt4 key购买 nike

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

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

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

还有其他有用的提示吗?

最佳答案

OP 的猜想是正确的 arg$1是包含捕获值的 lambda 对象的字段。答案来自 lukeg在让 lambda 元工厂转储其代理类方面走在正确的轨道上。 (+1)

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

(大多数(如果不是全部)信息适用于 Oracle JDK 和 OpenJDK。它可能不适用于不同的 JDK 实现。此外,这在 future 可能会发生变化。这应该适用于任何最新的 Oracle JDK 8 或 OpenJDK 8,尽管如此。它可能会继续在 JDK 9 中工作。)

首先,介绍一下背景。当编译包含 lambdas 的源文件时,javac将把 lambda 体编译成驻留在包含类中的合成方法。这些方法是私有(private)的和静态的,它们的名字类似于 lambda$<method>$<count>其中 method 是包含 lambda 的方法的名称,count 是一个顺序计数器,从源文件的开头(从零开始)对方法进行编号。

在运行时首次评估 lambda 表达式时,将调用 lambda 元工厂。这将生成一个实现 lambda 函数接口(interface)的类。它实例化这个类,将参数传递给函数式接口(interface)方​​法(如果有),将它们与任何捕获的值组合,并调用由 javac 编译的合成方法。如上所述。这个实例被称为“函数对象”或“代理”。

通过让 lambda 元工厂转储其代理类,您可以使用 javap反汇编字节码并将代理实例追溯到为其生成的 lambda 表达式。这可能最好通过一个例子来说明。考虑以下代码:

public class CaptureTest {
static List<IntSupplier> 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反汇编私有(private)方法。这个输出包括很多东西,但重要的部分是合成 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=<outputdir>java命令。这会导致 lambda 元工厂将其生成的类转储到命名目录(必须已经存在)。

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

这个类名上的计数器没有用,因为它表示由 lambda 元工厂生成的类的序列号,按照它们在运行时生成的顺序。了解运行时序列并不能告诉我们它在源代码中的来源。

然而,我们已经要求 lambda 元工厂转储它的类,所以我们可以去看看这个特定的类,看看它做了什么。确实,在输出目录中,有一个文件 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 数 2(从零开始),它是 bar() 中两个 lambda 表达式中的第一个方法。现在您可以返回 javap原始类的输出并使用来自 lambda 静态方法的行号信息来查找源文件中的位置。 CaptureTest.lambda$bar$2的行号信息方法指向第 29 行。这个位置的 lambda 是
    () -> o.hashCode()

哪里 o是一个自由变量,它捕获了 bar() 的参数之一。方法。

关于java - 从堆转储中的错误名称中查找 Java lambda,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41570839/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com