views:

236

answers:

2

I have a class A:

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

    public B getB() { return b; }
}

public interface B { void method(); }

The instance b has an implicit reference of the instance of its outer class (that can be referenced by this). Now another object gets a reference to this b via the getter method. This b cannot be garbage collected due to the reference.

Is there a way to get a possibility to allow a garbage collection of the enclosing A instance, maybe via resetting an explicit reference in the anonymous inner class?

+2  A: 

It is technically possible:

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

The javap inspection of the anonymous class:

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

The implementation does not rely on the field name being this$0 as I suspect this is a compiler implementation detail.

Potential problem areas:

  • A security manager may forbid the reflection code.
  • I don't think the Java platform defines exactly how the inner type refers to the outer. That is, it is a compiler implementation detail and it would be legal, if stupid, to have an intermediary wrapper in the field - in the presence of other fields, disambiguating the reference may be impossible.

In short, I would never do this.

If this is a concern, use a private static inner class:

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

  private final B b = new BImpl();

  public B getB() { return b; }
}
McDowell
Adding this comment so that I can see if the OP ends up accepting this hack, regardless of your comments :-) (and no, if you get downvoted, it wasn't by me; although I wish you'd delete the answer)
kdgregory
Thx for the answer. It's clear that the static inner class is the better option.And no, I would never use that option either. You could add some 'Bad" Tag...
boutta
@kdgregory: I only wanted to know a possibility, not use it. And yes this answer is what I was looking for.
boutta
From both your comments and my own, I think that is clear to anyone reading this that _if you want to hang yourself, here is some rope!_
McDowell
+2  A: 

After your code calls B ref = (new A()).getB() the Java heap will contain a variable ref which point to the same anonymous object as (new A()).b which in turn has an internal reference to its enclosing new A(). No other references to the new A() object exist.

Your question is, how can we force a garbage collection of the A object while keeping the anonymous b object alive? The answer is you cannot, because if you could, what would happen with the code in b that uses the internal reference to A?

If you know that the code of your B class does not reference its enclosing class, you can declare it static, meaning it will not receive an internal reference to its enclusing class. To do this you need to make it a nested class, as you cannot spaecify anonymous classes to be static:

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

If your code calls B ref = (new A()).getB() in this situation the new A() object will be available for garbage collection as no reference to it exists.

rsp
+1 - and I believe that FindBugs will tell you if you have an inner class that could be a nested (I know it will do that with instance/static methods)
kdgregory
Gotta like FindBugs: SIC: Should be a static inner class: http://findbugs.sourceforge.net/bugDescriptions.html#SIC_INNER_SHOULD_BE_STATIC
trashgod