英文:
Is there any logic to dereferencing a variable for use within a lambda to allow garbage collection?
问题
我有一个占用内存较多的对象(foo),其中包含一个较小的对象(bar)。我希望确保 foo 被垃圾回收,但在程序的生命周期内需要访问 lambda 中的 hello
字段。
我想知道是否有任何逻辑,可以先将 bar 解引用为自己的变量,然后再创建我的 lambda。我认为这样可能允许 foo 被垃圾回收,但不太确定。
也就是说,在以下示例中,因为我只使用了 bar.hello
,所以在创建 lambda 前,是否在执行 B
而不是 A
有任何逻辑,以允许 foo
被垃圾回收?第一个 lambda(A)是否隐含地持有对 foo 的引用,而第二个 lambda(B)是否消除了这个引用?
class Bar { // 在 lambda 中使用
String hello;
}
class Foo { // 占用内存较多的类
Bar bar;
String world;
}
Foo foo = new Foo();
// A(通过 foo 访问 bar,foo 是否仍然保留在内存中?)
run(() -> System.out.println(foo.bar.hello));
// B(foo 不应再有引用,应该被垃圾回收?)
Bar bar = foo.bar;
run(() -> System.out.println(bar.hello));
英文:
I have a memory-heavy object (foo) and a smaller object (bar) held within. I want to make sure foo is garbage collected but need access to the hello
field within my lambda for the lifetime of the program.
I am wondering if there is any logic to first dereferencing bar into its own variable before creating my lambda. I believe that this may allow foo to be garbage collected but am not certain.
I.e. in the following example, as I am only using bar.hello
, is there any logic in doing B
over A
to allow foo
to be garbage collected? Does the first lambda (A) implicitly hold a reference to foo, and does the second (B) remove this reference?
class Bar { // Use within lambda
String hello;
}
class Foo { // Memory-heavy class
Bar bar;
String world;
}
Foo foo = new Foo();
// A (access bar through foo, will foo remain in memory?)
run(() -> System.out.println(foo.bar.hello));
// B (foo should have no more references, should be GC'd?)
Bar bar = foo.bar;
run(() -> System.out.println(bar.hello));
答案1
得分: 0
适当地说,foo
是一个变量,它引用了一个类 Foo
的实例。
但是,是的,改变 lambda 表达式将使得类 Foo
的实例能够进行垃圾回收。
public class LambdaTest {
public static class OuterClass {
public InnerClass inner;
}
public static class InnerClass {
public String value;
}
public static void main(String[] args) {
// 尝试在这里定义 value 打印 lambda 会失败,
// 因为 'outer1' 既不是 final,也不是 effectively final。
//
// 要求 'outer1' 是 final 会启用对 outer 的安全内联。
// 否则,lambda 会保持对堆栈帧的引用。
// 将堆栈帧放在堆上的语言不会这样做。Java 不是其中之一。
// OuterClass outer1 = null;
//
// Runnable valuePrinter =
// () -> { System.out.println("Value [ " + outer.inner.value + " ]"); };
// outer1 = new OuterClass();
OuterClass outer1 = new OuterClass();
// 'outer1' 的值被内联到 lambda 中。
//
// 这创建了一个对 'outer1' 值的新引用,这将阻止
// OuterClass 实例被垃圾回收。
Runnable valuePrinter1 =
() -> { System.out.println("Inside lambda1 [ " + outer1.inner.value + " ]"); };
// 对 'outer1' 的新赋值会导致 lambda 定义失败。
// outer = new OuterClass();
outer1.inner = new InnerClass();
outer1.inner.value = "TestValue1";
System.out.println("Outside lambda1 [ " + outer1.inner.value + " ]");
valuePrinter1.run();
// 虽然 'outer1' 的值被内联,但 'outer.inner.value' 表达式的其他部分没有被内联。
//
// 对 'value' 和 'inner' 引用的更改对 lambda 是可见的。
outer1.inner.value = "TestValue2";
System.out.println("Outside lambda1 [ " + outer1.inner.value + " ]");
valuePrinter1.run();
outer1.inner = new InnerClass();
outer1.inner.value = "TestValue3";
System.out.println("Outside lambda1 [ " + outer1.inner.value + " ]");
valuePrinter1.run();
OuterClass outer2 = new OuterClass();
InnerClass inner2 = new InnerClass();
outer2.inner = inner2;
inner2.value = "TestValue4";
// 'inner2' 引用的 InnerClass 实例没有对 'outer2' 引用的 OuterClass 实例的引用。
//
// lambda 'valuePrinter2' 内联了 'inner2' 持有的引用。
//
// 对 'valuePrinter2' 的活动引用不会阻止由 'outer2' 引用的值被垃圾回收。
System.out.println("Outside lambda2 [ " + inner2.value + " ]");
Runnable valuePrinter2 =
() -> { System.out.println("Inside lambda2 [ " + inner2.value + " ]"); };
valuePrinter2.run();
}
// 输出:
//
// Outside lambda1 [ TestValue1 ]
// Inside lambda1 [ TestValue1 ]
// Outside lambda1 [ TestValue2 ]
// Inside lambda1 [ TestValue2 ]
// Outside lambda1 [ TestValue3 ]
// Inside lambda1 [ TestValue3 ]
// Outside lambda2 [ TestValue4 ]
// Inside lambda2 [ TestValue4 ]
}
英文:
Properly speaking, foo
is a variable, which references a class Foo
instance.
But yes, changing the lambda expression will enable garbage collection of the class Foo
instance.
public class LambdaTest {
public static class OuterClass {
public InnerClass inner;
}
public static class InnerClass {
public String value;
}
public static void main(String[] args) {
// Attempting to define the value printer lambda here fails,
// since 'outer1' is neither final nor effectively final.
//
// Requiring 'outer1' to be final enables safe inlining of
// outer's value. Otherwise, a reference to stack frame
// would be held by the lambda. Languages which place stack frames
// on the heap don't do this. java is not one of those language.
// OuterClass outer1 = null;
//
// Runnable valuePrinter =
// () -> { System.out.println("Value [ " + outer.inner.value + " ]"); };
// outer1 = new OuterClass();
OuterClass outer1 = new OuterClass();
// The value of 'outer1' is inlined in the lambda.
//
// This creates a new reference to the value of 'outer1', which will prevent
// that OuterClass instance from being garbage collected.
Runnable valuePrinter1 =
() -> { System.out.println("Inside lambda1 [ " + outer1.inner.value + " ]"); };
// A new assignment to 'outer1' would cause the lambda definition to
// fail.
// outer = new OuterClass();
outer1.inner = new InnerClass();
outer1.inner.value = "TestValue1";
System.out.println("Outside lambda1 [ " + outer1.inner.value + " ]");
valuePrinter1.run();
// While the 'outer1' value was inlined, the other parts of the
// 'outer.inner.value' expression were not.
//
// Changes made to the 'value' and 'inner' references are visible to
// the lambda.
outer1.inner.value = "TestValue2";
System.out.println("Outside lambda1 [ " + outer1.inner.value + " ]");
valuePrinter1.run();
outer1.inner = new InnerClass();
outer1.inner.value = "TestValue3";
System.out.println("Outside lambda1 [ " + outer1.inner.value + " ]");
valuePrinter1.run();
OuterClass outer2 = new OuterClass();
InnerClass inner2 = new InnerClass();
outer2.inner = inner2;
inner2.value = "TestValue4";
// The InnerClass instance referenced by 'inner2' has no reference to
// the OuterClass instance referenced by 'outer2'.
//
// The lambda 'valuePrinter2' inlines the reference held by 'inner2'.
//
// A live reference to 'valuePrinter2' will not prevent the value referenced
// by 'outer2' from being garbage collected.
System.out.println("Outside lambda2 [ " + inner2.value + " ]");
Runnable valuePrinter2 =
() -> { System.out.println("Inside lambda2 [ " + inner2.value + " ]"); };
valuePrinter2.run();
}
// Output:
//
// Outside lambda1 [ TestValue1 ]
// Inside lambda1 [ TestValue1 ]
// Outside lambda1 [ TestValue2 ]
// Inside lambda1 [ TestValue2 ]
// Outside lambda1 [ TestValue3 ]
// Inside lambda1 [ TestValue3 ]
// Outside lambda2 [ TestValue4 ]
// Inside lambda2 [ TestValue4 ]
}
答案2
得分: 0
以下是翻译好的部分:
这是一个有趣的问题,我个人认为。
以下是我如何稍微简化了代码库:
public class DeleteMe {
public static void main(String[] args) {
Foo foo = new Foo();
Thread t = new Thread(() -> System.out.println(foo.bar.hello));
t.start();
}
static class Bar { // 在 lambda 内使用
String hello = "hello";
}
static class Foo { // 占用内存的类
Bar bar = new Bar();
}
}
现在,这是一个捕获 lambda,如果你运行它(使用特殊标志:-Djdk.internal.lambda.dumpProxyClasses=/some/path/goes/here
),并查看反编译的代码,你会注意到它“捕获”了一个 Foo
实例。用更简单的话说,该 lambda 将在内部转换为一个类(实现了 Runnable
),它会将一个 Foo
实例作为参数。
如果你将代码更改为:
Foo foo = new Foo();
Bar bar = foo.bar;
Thread t = new Thread(() -> System.out.println(bar.hello));
t.start();
以相同的标志运行它,然后进行反编译 - 你会看到现在捕获了一个 Bar
实例。
这改变了情况。
这意味着当碰到这行时:
run(() -> System.out.println(bar.hello));
Foo
实例就有资格进行垃圾回收,这正是你想要的。
另一个代码块:
Thread t = new Thread(() -> System.out.println(foo.bar.hello));
如我所说,会被转换为一个实现了 Runnable
接口的 class
,大致会像这样:
class RunnableImpl implements Runnable {
private final Foo foo;
public RunnableImpl(Foo foo) {
this.foo = foo;
}
public void run(){
Bar bar = foo.bar;
System.out.println(bar.hello);
}
}
虽然不完全是这样,但对于这个例子来说,我将其翻译得不完全正确也没有关系。这意味着 Thread
实例内部将会存储一个 Runnable
实例(RunnableImpl
的一个实例),而根据上述所见,RunnableImpl
实例内部将会有一个 Foo
的实例。
因此,在第二种情况下,只有当线程结束时,foo
才能被回收,这可能会比线程启动前的时间要晚得多。
英文:
That is one interesting question, imho.
Here is how I've simplified the code base a bit:
public class DeleteMe {
public static void main(String[] args) {
Foo foo = new Foo();
Thread t = new Thread(() -> System.out.println(foo.bar.hello));
t.start();
}
static class Bar { // Use within lambda
String hello = "hello";
}
static class Foo { // Memory-heavy class
Bar bar = new Bar();
}
}
Now this is a capturing lambda and if you run it (with a special flag: -Djdk.internal.lambda.dumpProxyClasses=/some/path/goes/here
) and look at the de-compiled code, you will notice that it "captures" a Foo
instance. In somehow simpler words, that lambda will be transformed into a class (that implements Runnable
) internally that will take a Foo
instance as an argument.
If you change that code to:
Foo foo = new Foo();
Bar bar = foo.bar;
Thread t = new Thread(() -> System.out.println(bar.hello));
t.start();
Run that with the same flag, decompile - you will see that a Bar
instance is now captured.
This changes things.
This means that Foo
instance is eligible for GC, as soon as this line is hit:
run(() -> System.out.println(bar.hello));
And this is exactly what you wanted.
The other code that has :
Thread t = new Thread(() -> System.out.println(foo.bar.hello));
as I said, will be transformed to a class
that implements Runnable
and will sort of look like this:
class RunnableImpl implements Runnable {
private final Foo foo;
public RunnableImpl(Foo foo) {
this.foo = foo;
}
public void run(){
Bar bar = foo.bar;
System.out.println(bar.hello);
}
}
It is not exactly like this, but it does not matter (for this example) if I translate it entirely correct. This means that the Thread
instance, will have a Runnable
instance stored inside it (an instance of RunnableImpl
), which in turn as seen above will have an instance of Foo
.
So in the second variant, foo
can only be collected when the Thread ends; which potentially can be a lot later in time, then before the thread even starts.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论