5

I'm a bit confused with java 8's 'closure'. It supposedly closes over values. Consider the following class.

public class SomeClassWithLargeMemoryFootprint {
     //some state
     private SomeObject someObj;
     //some more state

     public void doSomething(SomeAsyncHelper helper) {
         helper.doAsync( () -> {
                             //some super slow operation
                             int foo = someObj.whatever();
                             //some more stuff    
                       });
     }
}

.

//Let's assume SomeAsyncHelper.doAsync takes a VoidRunner that looks like below
interface VoidRunner {
    void apply();
}

Question is, can an instance of SomeClassWithLargeMemoryFootprint be GC'd when the async helper is still working? It's clear to me that "someObj" can't be GC'd as it is required by the lambda in doSomething(). What about the rest of the state?

Also, consider the following variant where we invoke a member method of the containing class:

public class SomeClassWithLargeMemoryFootprint {
     //some state
     private SomeObject someObj;
     //some more state

     public void doSomething(SomeAsyncHelper helper) {
         helper.doAsync( () -> {
                //do something
                memberMethod();
                //do something else
                });
     }

     private void memberMethod() {
         //do something
     }

}

what now? How does the 'helper' know how to execute "memberMethod"? Does it get a reference to the instance of SomeClassWithLargeMemoryFootprint? What would be the GC sequence?

SvS
  • 51
  • 4
  • As far as I know, The GCs in Java use reference counting and lambdas in Java are just some syntactic sugar for inlined classes. Long story short: It works as if you would implement all of this with an inlined class. Since inlined classes are private classes, you can always access the surrounding obect (and its attributes and methods). Reference counting takes care of the rest and the correct release of the object (aka. after the lambda has been executed). – Turing85 Oct 31 '17 at 20:30
  • **This is indeed a duplicate, but of a different question: [Do Java8 lambdas maintain a reference to their enclosing instance like anonymous classes?](https://stackoverflow.com/a/28447015/581205).** See also https://stackoverflow.com/a/23991339/581205. – maaartinus Nov 07 '17 at 04:27

1 Answers1

3

can an instance of SomeClassWithLargeMemoryFootprint be GC'd when the async helper is still working?

No. The VoidRunner instance has a strong reference to its enclosing SomeClassWithLargeMemoryFootprint object, and is itself referenced somehow by your asynchronous task executor. So it can't be GCed.

What about the rest of the state?

The rest of the state is referenced by the instance of SomeClassWithLargeMemoryFootprint, so it can't be GCed.

How does the 'helper' know how to execute "memberMethod"

The same way it knows how to access someObj(i.e. this.someObj) in the first example: it has a reference to its enclosing SomeClassWithLargeMemoryFootprint object (i.e. this).

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • Cool.. thanks! The fact that the anonymous classes hold a reference to the enclosing class makes it clear. – SvS Oct 31 '17 at 20:36
  • A follow up question: Is there a way to break the strong reference to the enclosing class when I don't access any of the enclosing class's members in the lambda? By that, I mean a neat way, not too divergent from the convenience of using a lambda. Not a concrete class that implements the lambda interface. – SvS Oct 31 '17 at 20:50
  • @Saivivekh Swaminathan: when you don’t access members of the surrounding instances, the lambda expression will not capture a reference to the surrounding instance. There is no need to “break the strong reference” in that case. In your first variant, where you only call a method on `someObj`, you can avoid the capturing of `this`, by first reading the target object into a local variable, `SomeObject localVar = this.someObj;`, and use only the local variable within the lambda: `helper.doAsync( () -> { … int foo = localVar.whatever(); … });` only capturing the `SomeObject` instance via `localVar` – Holger Nov 01 '17 at 14:58
  • @Holger: thank you for the response. That means whoever said lambdas are just syntactic sugar for inline anonymous classes is wrong, right? Anonymous inline classes apparently always get a reference to the enclosing class according to https://stackoverflow.com/questions/5054360/do-anonymous-classes-always-maintain-a-reference-to-their-enclosing-instance. – SvS Nov 06 '17 at 01:22
  • 2
    @SaivivekhSwaminathan: exactly; lambda expressions are more than syntactic sugar. See [Do Java8 lambdas maintain a reference to their enclosing instance like anonymous classes?](https://stackoverflow.com/q/28446626/2711488) and [Does a lambda expression create an object on the heap every time it's executed?](https://stackoverflow.com/q/27524445/2711488) – Holger Nov 06 '17 at 09:29