Разрешить сборку мусора для класса, в то время как на анонимный экземпляр внутреннего класса есть ссылки в другом месте? - PullRequest
4 голосов
/ 13 января 2010

У меня есть класс А:

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. Этот b не может быть удален из-за ссылки.

Есть ли способ получить возможность разрешить сбор мусора вмещающего экземпляра A, возможно, путем сброса явной ссылки в анонимном внутреннем классе?

Ответы [ 2 ]

3 голосов
/ 13 января 2010

После того, как ваш код вызовет B ref = (new A()).getB(), куча Java будет содержать переменную ref, которая указывает на тот же анонимный объект, что и (new A()).b, который, в свою очередь, имеет внутреннюю ссылку на вложенную new A(). Других ссылок на объект new A() не существует.

Ваш вопрос: как мы можем форсировать сборку мусора объекта A при сохранении анонимного объекта b живым? Ответ: вы не можете, потому что если бы вы могли, что бы случилось с кодом в 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() будет доступен для сборки мусора, поскольку ссылки на него не существует.

3 голосов
/ 13 января 2010

Технически возможно:

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