Search code examples
javalambdagarbage-collection

Can Java lambda expressions be guaranteed not to hold a reference to `this`?


If a lambda expression does not refer to any methods or fields of the surrounding instance, does the language guarantee that it doesn't hold a reference to this?

In particular, I want to use lambda expressions to implement java.lang.ref.Cleaner actions. For example:

import static some.Global.cleaner;

public class HoldsSomeResource {
    private final Resource res;
    private final Cleanable cleanup;

    public HoldsSomeResource(Resource res) {
        this.res = res;
        cleanup = cleaner.register(this, () -> res.discard());
    }

    public void discard() {
        cleanup.clean();
    }
}

Clearly, it would be bad if the lambda expression implementing the cleanup action were to hold a reference to this, since it would then never become unreachable. It seems to work when I test it right now, but I can't find the obvious reference in the JLS that it is guaranteed to be safe, so I'm slightly worried that I might run into problems in alternative and/or future Java implementations.


Solution

  • The specification does indeed not mention this behavior, but there is a statement in this document from Brian Goetz:

    References to this — including implicit references through unqualified field references or method invocations — are, essentially, references to a final local variable. Lambda bodies that contain such references capture the appropriate instance of this. In other cases, no reference to this is retained by the object.

    While this isn’t the official specification, Brian Goetz is the most authoritative person we can have to make such a statement.

    This behavior of lambda expressions is as intentional as it can be. The cited text continues with

    This has a beneficial implication for memory management: while inner class instances always hold a strong reference to their enclosing instance, lambdas that do not capture members from the enclosing instance do not hold a reference to it. This characteristic of inner class instances can often be a source of memory leaks.

    Note that this other behavior, inner class instances always holding an implicit reference to the outer this instance, also does not appear anywhere in the specification. So when even this behavior, causing more harm than good if ever being intentional, is taken for granted despite not appearing in the specification, we can be sure that the intentionally implemented behavior to overcome this issue will never be changed.

    But if you’re still not convinced, you may follow the pattern shown in this answer or that answer of delegating to a static method to perform the Cleaner registration. This has the benefit of also preventing accidental use of members while still being simpler than the documentation’s suggested use of a nested static class.