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

Lau*_*ves 28 java garbage-collection anonymous-class

我正在处理一些代码,其中一个对象"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);
      }
    };
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

Lau*_*ves 30

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

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

编译时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

}
Run Code Online (Sandbox Code Playgroud)

putfield行显示对封闭实例的引用由构造函数存储在字段this$0(类型Outer)中,即使此字段永远不会再次使用.

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

  • @eckes正确,十年后,从JDK 18开始,我们终于摆脱了过时的外部对象引用,[JDK-8271717](https://bugs.openjdk.org/browse/JDK-8271717) (3认同)
  • @LaurenceGonsalves如果你正在更新它,值得注意的是,lambda表达式没有上一节中提到的这个问题,因此你可以用lambda表达式替换匿名内部类是可行的,以避免从Java 8开始不必要的外部对象引用。(这又是 JLS 中未指定的一点,但只是编译器实现它的方式) (2认同)

小智 5

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

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());
    }
}
Run Code Online (Sandbox Code Playgroud)