4

我有一个A类:

public class A {
    private B b = new B() { public void method() { do something } };

    public B getB() { return b; }
}

public interface B { void method(); }

该实例b具有对其外部类实例的隐式引用(可以由 引用this)。现在另一个对象b通过 getter 方法获得对 this 的引用。由于引用,这b不能被垃圾收集。

有没有办法允许对封闭A实例进行垃圾收集,可能是通过重置匿名内部类中的显式引用?

4

2 回答 2

3

技术上是可行的:

public class HasInner {
  public static interface Foo {}

  private static <T> T release(T instance, Object ref) {
    try {
      Class<?> type = instance.getClass();
      for (Field field : type.getFields()) {
        if (!field.isAccessible()) {
          field.setAccessible(true);
        }
        if (field.get(instance) == ref) {
          field.set(instance, null);
        }
      }
    } catch (IllegalAccessException e) {
      throw new IllegalStateException(e);
    }
    return instance;
  }

  public Foo makeFoo() {
    return release(new Foo() {}, this);
  }

  public static void main(String[] args) {
    new HasInner().makeFoo();
  }
}

匿名类的javap检查:

Compiled from "HasInner.java"
final class HasInner$1 extends java.lang.Object implements HasInner$
Foo{
    final HasInner this$0;
    HasInner$1(HasInner);
}

该实现不依赖于字段名称,this$0因为我怀疑这是编译器实现细节。

潜在问题领域:

  • 安全管理器可以禁止反射代码。
  • 认为Java 平台并没有准确定义内部类型如何引用外部。也就是说,它是一个编译器实现细节,如果愚蠢的话,在该字段中有一个中间包装器是合法的——在存在其他字段的情况下,消除引用的歧义可能是不可能的。

简而言之,我永远不会这样做

如果这是一个问题,请使用私有静态内部类

public class A {
  private static class BImpl implements B {
    @Override public void method() {
    }
  }

  private final B b = new BImpl();

  public B getB() { return b; }
}
于 2010-01-13T12:39:31.133 回答
3

在您的代码调用之后B ref = (new A()).getB(),Java 堆将包含一个变量,该变量ref指向同一个匿名对象,(new A()).b而后者又具有对其封闭的new A(). new A()不存在对该对象的其他引用。

您的问题是,我们如何在保持匿名 b 对象存活的同时强制对 A 对象进行垃圾收集?答案是你不能,因为如果可以的话,b 中使用对 A 的内部引用的代码会发生什么?

如果您知道 B 类的代码没有引用其封闭类,则可以将其声明为静态,这意味着它不会收到对其封闭类的内部引用。为此,您需要将其设为嵌套类,因为您不能将匿名类指定为静态:

public class A {
    static class Bimpl implements B { public void method() { do something } };

    private B b = new Bimpl();

    public B getB() { return b; }
}

public interface B { void method(); }

如果您的代码B ref = (new A()).getB()在这种情况下调用该对象,则该new A()对象将可用于垃圾回收,因为不存在对它的引用。

于 2010-01-13T12:47:34.283 回答