30

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

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

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);
      }
    };
  }
}

然而,我突然想到,这实际上可能无法按预期工作,因为内部类通常会保留对其封闭对象的引用。我不想在这里引用封闭类,因为我希望在Callable仍然可以访问的时候收集封闭对象。

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

那么......即使匿名内部类的实例从未实际使用封闭实例引用,它是否会保留对其封闭实例的引用?

4

3 回答 3

31

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

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."<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

}

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

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

于 2011-02-20T00:07:59.537 回答
6

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

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());
    }
}
于 2014-01-30T09:56:21.233 回答
1

静态替代方案(在这种情况下)不会大很多(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在这种情况下传递的对象是否包含对外部实例的引用(如果不需要)。

于 2013-12-18T00:52:55.117 回答