Подтвердить что ты не робот

Разве анонимные классы * всегда * поддерживают ссылку на их прилагаемый экземпляр?

Я работаю с некоторым кодом, где один объект, "foo", создает другой объект, "bar" и передавая ему Callable. После этого foo вернется бар, а затем я хочу, чтобы foo стал недоступным (то есть: доступно для сбор мусора).

Моя первоначальная мысль заключалась в том, чтобы просто анонимно создать Callable. например:

class Foo {
  ...

  public Bar createBar() {
    final int arg1 = ...
    final int arg2 = ...
    final int arg3 = ...
    return new Callable<Baz>() {
      @Override
      public Baz call() {
        return new Baz(arg1, arg2, arg3);
      }
    };
  }
}

Мне пришло в голову, что это может работать не так, как хотелось бы, поскольку внутренний класс обычно сохраняет ссылку на свой охватывающий объект. Мне не нужна ссылка на класс включения здесь, потому что я хочу, чтобы охватывающий объект был собранных, пока Callable все еще доступен.

С другой стороны, обнаруживая, что охватывающий экземпляр никогда фактически не упоминается должно быть довольно тривиально, поэтому, возможно, компилятор Java достаточно умен чтобы не включать ссылку в этом случае.

Итак... экземпляр анонимного внутреннего класса удерживает ссылку на свой охватывающий экземпляр, даже если он никогда не использует ссылка на экземпляр экземпляра?

4b9b3361

Ответ 1

Да, экземпляры анонимных внутренних классов сохраняют ссылки на их охватывающие экземпляры, даже если эти ссылки никогда не использовался. Этот код:

public class Outer {
  public Runnable getRunnable() {
    return new Runnable() {
      public void run() {
        System.out.println("hello");
      }
    };
  }
}

При компиляции с javac генерируются два файла класса, Outer.class и Outer$1.class. Разборка последнего, анонимного внутреннего класса, с javap -c дает:

Compiled from "Outer.java"
class Outer$1 extends java.lang.Object implements java.lang.Runnable{
final Outer this$0;

Outer$1(Outer);
  Code:
   0:   aload_0
   1:   aload_1
   2:   putfield        #1; //Field this$0:LOuter;
   5:   aload_0
   6:   invokespecial   #2; //Method java/lang/Object."<init>":()V
   9:   return

public void run();
  Code:
   0:   getstatic       #3; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   ldc     #4; //String hello
   5:   invokevirtual   #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   8:   return

}

Строка putfield показывает, что ссылка на прилагаемый экземпляр хранится в поле this$0 (типа Outer) конструктором даже если это поле больше никогда не используется.

Это неудачно, если вы пытаетесь создать небольшую потенциальную долгоживущие объекты с анонимными внутренними классами, поскольку они будут (потенциально большой) экземпляр экземпляра. Обходным путем является использование экземпляра статического класса (или класса верхнего уровня). Это, к сожалению, более многословно.

Ответ 2

Вы можете легко превратить вложенный анонимный класс в "статический" анонимный класс, введя статический метод в свой класс.

import java.util.ArrayList;


public class TestGC {
    public char[] mem = new char[5000000];
    public String str = "toto";

    public interface Node {
        public void print();
    }

    public Node createNestedNode() {
        final String str = this.str;
        return new Node() {
            public void print() {
                System.out.println(str);
            }
        };
    }

    public static Node createStaticNode(TestGC test) {
        final String str = test.str;
        return new Node() {
            public void print() {
                System.out.println(str);
            }
        };
    }

    public Node createStaticNode() {
        return createStaticNode(this);
    }

    public static void main(String... args) throws InterruptedException {
        ArrayList<Node> nodes = new ArrayList<Node>();
        for (int i=0; i<10; i++) {
            // Try once with createNestedNode(), then createStaticNode()
            nodes.add(new TestGC().createStaticNode());
            System.gc();
            //Thread.sleep(200);
            System.out.printf("Total mem: %d  Free mem: %d\n", Runtime.getRuntime().totalMemory(), Runtime.getRuntime().freeMemory());
        }
        for (Node node : nodes)
            node.print();
        nodes = null;
        System.gc();
        //Thread.sleep(200);
        System.out.printf("Total mem: %d  Free mem: %d\n", Runtime.getRuntime().totalMemory(), Runtime.getRuntime().freeMemory());
    }
}

Ответ 3

Статическая альтернатива (в данном случае) не намного больше (1 строка):

public class Outer {
  static class InnerRunnable implements Runnable {
      public void run() {
        System.out.println("hello");
      }
    }
  public Runnable getRunnable() {
    return new InnerRunnable();
  }
}

BTW: если вы используете Lambda в Java8, никакого вложенного класса не будет создан. Однако я не уверен, что объекты CallSite, которые передаются в этом случае, содержат ссылку на внешний экземпляр (если это не требуется).