匿名类*总是*保持对其封闭实例的引用吗?

我正在使用一些代码,其中一个对象“foo”正在创建另一个对象“bar”,并将其传递给Callable 。 在此foo之后将返回bar,然后我希望foo变得无法访问(即:可用于垃圾收集)。

我最初的想法是匿名创建Callable 。 例如:

 class Foo { ... public Bar createBar() { final int arg1 = ... final int arg2 = ... final int arg3 = ... return new Callable() { @Override public Baz call() { return new Baz(arg1, arg2, arg3); } }; } } 

在我看来,这可能实际上并不按预期工作,因为内部类通常保持对其封闭对象的引用。 我不希望在这里引用封闭类,因为我希望在Callable仍可访问时收集封闭对象。

另一方面,检测到实际上从未引用封闭实例应该是非常简单的,因此Java编译器可能足够聪明,在这种情况下不包括引用。

那么……一个匿名内部类的实例是否会持有对其封闭实例的引用,即使它实际上从未使用封闭的实例引用?

是的,匿名内部类的实例持有对其封闭实例的引用,即使这些引用从未实际使用过。 这段代码:

 public class Outer { public Runnable getRunnable() { return new Runnable() { public void run() { System.out.println("hello"); } }; } } 

使用javac生成两个类文件, Outer.classOuter$1.class 。 用javap -c反汇编后者,匿名内部类:

 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."":()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 } 

putfield行显示对封闭实例的引用正被构造函数存储在this$0 (类型为Outer )的字段中,即使此字段从未再次使用过。

如果您尝试使用匿名内部类创建小的可能长寿命的对象,因为它们将保留在(可能很大的)封闭实例上,这是很不幸的。 解决方法是使用静态类(或顶级类)的实例。 不幸的是,这更加冗长。

通过在类中引入静态方法,您可以轻松地将嵌套的匿名类转换为“静态”匿名类。

 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 nodes = new ArrayList(); 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()); } } 

静态替代(在这种情况下)不是更大(1行):

 public class Outer { static class InnerRunnable implements Runnable { public void run() { System.out.println("hello"); } } public Runnable getRunnable() { return new InnerRunnable(); } } 

顺便说一句:如果在Java8中使用Lambda,则不会生成嵌套类。 但是我不确定在这种情况下传递的CallSite对象是否持有对外部实例的引用(如果不需要)。