【问题标题】:Allowing garbage collection of a class while an anonymous inner class instance is referenced elsewhere?允许在其他地方引用匿名内部类实例时对类进行垃圾收集?
【发布时间】:2010-01-13 11:26:39
【问题描述】:

我有一个 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 引用)。现在另一个对象通过 getter 方法获得了对这个 b 的引用。由于引用,此b 无法被垃圾收集。

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

【问题讨论】:

    标签: java garbage-collection inner-classes


    【解决方案1】:

    技术上是可行的:

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

    【讨论】:

    • 添加此评论,以便我可以查看 OP 是否最终接受了这个 hack,不管你的 cmets :-) (不,如果你被否决了,那不是我的;虽然我希望您删除答案)
    • 谢谢你的答案。很明显,静态内部类是更好的选择。不,我也永远不会使用该选项。您可以添加一些“坏”标签...
    • @kdgregory:我只想知道一种可能性,而不是使用它。是的,这个答案就是我想要的。
    • 无论是你的还是我自己的,我认为任何阅读本文的人都清楚如果你想上吊,这里有绳子!
    【解决方案2】:

    在您的代码调用B ref = (new A()).getB() 之后,Java 堆将包含一个变量ref,它指向与(new A()).b 相同的匿名对象,而(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() 对象将可用于垃圾回收,因为不存在对它的引用。

    【讨论】:

    • +1 - 我相信 FindBugs 会告诉你是否有一个可以嵌套的内部类(我知道它会用实例/静态方法做到这一点)
    • 一定喜欢 FindBugs:SIC:应该是静态内部类:findbugs.sourceforge.net/…
    猜你喜欢
    • 2013-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-20
    相关资源
    最近更新 更多