Permitir a coleta de lixo de uma aula enquanto uma instância de classe interna anônima é referenciada em outro lugar?

StackOverflow https://stackoverflow.com/questions/2056205

Pergunta

Eu tenho uma classe A:

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

    public B getB() { return b; }
}

public interface B { void method(); }

A instância b tem uma referência implícita da instância de sua classe externa (que pode ser referenciada por this). Agora outro objeto recebe uma referência a isso b através do método getter. este b não pode ser coletado de lixo devido à referência.

Existe uma maneira de obter a possibilidade de permitir uma coleta de lixo dos anexos A Por exemplo, talvez através da redefinição de uma referência explícita na classe interna anônima?

Foi útil?

Solução

É tecnicamente possível:

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

o javap Inspeção da aula anônima:

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

A implementação não depende do nome de campo sendo this$0 Como eu suspeito, este é um detalhe de implementação do compilador.

Áreas problemáticas em potencial:

  • Um gerente de segurança pode proibir o código de reflexão.
  • Eu não acho A plataforma Java define exatamente como o tipo interno se refere à externa. Ou seja, é um detalhe de implementação do compilador e seria legal, se estúpido, ter um invólucro intermediário no campo - na presença de outros campos, desambiguar a referência pode ser impossível.

Em suma, eu faria nunca faça isso.

Se isso é uma preocupação, Use uma classe interna estática privada:

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

  private final B b = new BImpl();

  public B getB() { return b; }
}

Outras dicas

Após as chamadas do seu código B ref = (new A()).getB() O heap java conterá uma variável ref que aponta para o mesmo objeto anônimo que (new A()).b que por sua vez tem uma referência interna ao seu anexo new A(). Nenhuma outra referência ao new A() Objeto existe.

Sua pergunta é: como podemos forçar uma coleção de lixo do objeto A, mantendo o objeto B anônimo vivo? A resposta é que você não pode, porque se pudesse, o que aconteceria com o código em B que usa a referência interna a A?

Se você souber que o código da sua classe B não faz referência à sua classe de anexo, pode declará -lo estático, o que significa que não receberá uma referência interna à sua classe de cercadores. Para fazer isso, você precisa torná -lo uma aula aninhada, pois não pode spaecificar aulas anônimas para ser estática:

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

Se o seu código ligar B ref = (new A()).getB() Nesta situação, o new A() O objeto estará disponível para a coleta de lixo, pois não existe referência a ele.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top