I'm working with some code where one object, "foo", is creating another
object, "bar", and passing it a Callable
. After this foo will return
bar, and then I want foo to become unreachable (ie: available for
garbage collection).
My initial thought was to just create the Callable
anonymously. eg:
class Foo {
...
public Bar createBar() {
final int arg1 = ...
final int arg2 = ...
final int arg3 = ...
return new Callable<Baz>() {
@Override
public Baz call() {
return new Baz(arg1, arg2, arg3);
}
};
}
}
It occurred to me that this might not actually work as desired, however,
as an inner class typically keeps a reference to its enclosing object.
I don't want a reference to the enclosing class here, because I want the enclosing object to be
collected while the Callable
is still reachable.
On the other hand,
detecting that the enclosing instance is never actually referred to
should be pretty trivial, so perhaps the Java compiler is smart enough
to not include a reference in that case.
So... will an instance of an anonymous inner class hold on to a
reference to its enclosing instance even if it never actually uses the
enclosing instance reference?
Yes, instances of anonymous inner classes hold on to a
reference to their enclosing instances even if these references are
never actually used. This code:
public class Outer {
public Runnable getRunnable() {
return new Runnable() {
public void run() {
System.out.println("hello");
}
};
}
}
When compiled with javac
generates two class files, Outer.class
and
Outer$1.class
. Disassembling the latter, the anonymous inner class,
with javap -c
yields:
Compiled from "Outer.java"
class Outer$1 extends java.lang.Object implements java.lang.Runnable{
final Outer this$0;
Outer$1(Outer);
Code:
0: aload_0
1: aload_1
2: putfield #1; //Field this$0:LOuter;
5: aload_0
6: invokespecial #2; //Method java/lang/Object."<init>":()V
9: return
public void run();
Code:
0: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #4; //String hello
5: invokevirtual #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
The putfield
line shows that a reference to the enclosing instance is
being stored in the field this$0
(of type Outer
) by the constructor
even though this field is never used again.
This is unfortunate if you're attempting to create small potentially
long-lived objects with anonymous inner classes as they'll hold onto the
(potentially large) enclosing instance. A workaround is to use an instance of a static class (or a top-level class) instead. This is unfortunately more verbose.
You can easily turn a nested anonymous-class into a "static" anonymous-class by introducing a static method in your class.
import java.util.ArrayList;
public class TestGC {
public char[] mem = new char[5000000];
public String str = "toto";
public interface Node {
public void print();
}
public Node createNestedNode() {
final String str = this.str;
return new Node() {
public void print() {
System.out.println(str);
}
};
}
public static Node createStaticNode(TestGC test) {
final String str = test.str;
return new Node() {
public void print() {
System.out.println(str);
}
};
}
public Node createStaticNode() {
return createStaticNode(this);
}
public static void main(String... args) throws InterruptedException {
ArrayList<Node> nodes = new ArrayList<Node>();
for (int i=0; i<10; i++) {
// Try once with createNestedNode(), then createStaticNode()
nodes.add(new TestGC().createStaticNode());
System.gc();
//Thread.sleep(200);
System.out.printf("Total mem: %d Free mem: %d\n", Runtime.getRuntime().totalMemory(), Runtime.getRuntime().freeMemory());
}
for (Node node : nodes)
node.print();
nodes = null;
System.gc();
//Thread.sleep(200);
System.out.printf("Total mem: %d Free mem: %d\n", Runtime.getRuntime().totalMemory(), Runtime.getRuntime().freeMemory());
}
}
The static alternative (in this case) is not much larger (1 line):
public class Outer {
static class InnerRunnable implements Runnable {
public void run() {
System.out.println("hello");
}
}
public Runnable getRunnable() {
return new InnerRunnable();
}
}
BTW: if you use a Lambda in Java8 there will be no nested class generated. However I am not sure if the CallSite
objects which get passed around in that case hold an reference to the outer instance (if not needed).