Java lambdas 堆转储 - lambda 实例未被垃圾收集
Java lambdas heap dump - Instance of lambda not getting garbage collected
我在 java 中生成应用程序时遇到了一些垃圾回收问题,我在其中使用 Stream.map 来 trim 列表中的所有元素。匿名 lambda class 的实例存在于堆转储中,即使封闭 class 的实例为 0,如可视化 VM 的快照所示。
LambdaTesting class:
class LambdaTesting {
protected List<String> values;
protected LambdaTesting(List<String> values) {
this.values = values;
}
public List<String> modify() {
return this.values.stream().map(x -> x.trim()).collect(Collectors.toList());
}
public List<String> modifyLocal() {
List<String> localValue = new ArrayList<>();
localValue.add("Local FOO ");
localValue.add("Local BAR ");
return localValue.stream().map(x -> x.trim()).collect(Collectors.toList());
}
}
创建 LambdaTesting 实例并调用这些方法的方法:
public List<String> testMethods() {
List<String> test = new ArrayList<>();
test.add("Global FOO ");
test.add(" GLOBAL BAR");
LambdaTesting lambdaTesting = new LambdaTesting(test);
lambdaTesting.modifyLocal();
lambdaTesting.modify();
}
线程转储是在调用 testMethods 后在下一行放置调试点后获取的。
为什么堆转储中仍然存在对 Lambda 的引用?
如 Does a lambda expression create an object on the heap every time it's executed? 中所述,非捕获 lambda 表达式将被记住并重复使用,这意味着它与创建它的代码永久关联。这与例如只要包含文字的代码还存在,其对象表示就会保留在内存中的字符串文字。
这是一个实现细节。不一定非要那样做,但参考实现以及所有常用的 JRE 都是那样做的。
非捕获 lambda 表达式是不使用周围上下文的(非常量)变量并且不使用 this
的 lambda 表达式,既不隐式也不显式。所以它没有状态,因此消耗少量内存。也没有可能创建关于其他对象的泄漏,因为对其他对象的引用是非捕获和捕获 lambda 表达式之间的区别,并且可能是捕获 lambda 表达式不被记住的主要原因。
因此,此类从未收集的实例的最大数量等于您应用程序中lambda表达式的总数,可能有几百甚至数千个,但与应用程序的对象总数相比仍然很小将永远创造。如 Function.identity() or t->t 中所述,将 lambda 表达式放入工厂方法而不是在源代码中重复它可以减少实例数。但鉴于对象总数相当少,这很少是一个问题。与已经提到的字符串文字或运行时已存在的 Class
个对象的数量进行比较…
我在 java 中生成应用程序时遇到了一些垃圾回收问题,我在其中使用 Stream.map 来 trim 列表中的所有元素。匿名 lambda class 的实例存在于堆转储中,即使封闭 class 的实例为 0,如可视化 VM 的快照所示。
LambdaTesting class:
class LambdaTesting {
protected List<String> values;
protected LambdaTesting(List<String> values) {
this.values = values;
}
public List<String> modify() {
return this.values.stream().map(x -> x.trim()).collect(Collectors.toList());
}
public List<String> modifyLocal() {
List<String> localValue = new ArrayList<>();
localValue.add("Local FOO ");
localValue.add("Local BAR ");
return localValue.stream().map(x -> x.trim()).collect(Collectors.toList());
}
}
创建 LambdaTesting 实例并调用这些方法的方法:
public List<String> testMethods() {
List<String> test = new ArrayList<>();
test.add("Global FOO ");
test.add(" GLOBAL BAR");
LambdaTesting lambdaTesting = new LambdaTesting(test);
lambdaTesting.modifyLocal();
lambdaTesting.modify();
}
线程转储是在调用 testMethods 后在下一行放置调试点后获取的。
为什么堆转储中仍然存在对 Lambda 的引用?
如 Does a lambda expression create an object on the heap every time it's executed? 中所述,非捕获 lambda 表达式将被记住并重复使用,这意味着它与创建它的代码永久关联。这与例如只要包含文字的代码还存在,其对象表示就会保留在内存中的字符串文字。
这是一个实现细节。不一定非要那样做,但参考实现以及所有常用的 JRE 都是那样做的。
非捕获 lambda 表达式是不使用周围上下文的(非常量)变量并且不使用 this
的 lambda 表达式,既不隐式也不显式。所以它没有状态,因此消耗少量内存。也没有可能创建关于其他对象的泄漏,因为对其他对象的引用是非捕获和捕获 lambda 表达式之间的区别,并且可能是捕获 lambda 表达式不被记住的主要原因。
因此,此类从未收集的实例的最大数量等于您应用程序中lambda表达式的总数,可能有几百甚至数千个,但与应用程序的对象总数相比仍然很小将永远创造。如 Function.identity() or t->t 中所述,将 lambda 表达式放入工厂方法而不是在源代码中重复它可以减少实例数。但鉴于对象总数相当少,这很少是一个问题。与已经提到的字符串文字或运行时已存在的 Class
个对象的数量进行比较…