Всегда ли анонимные классы * поддерживают * ссылку на свой включающий экземпляр? - PullRequest
27 голосов
/ 20 февраля 2011

Я работаю с некоторым кодом, где один объект, "foo", создает другой объект, "бар", и передавая его Callable. После этого фу вернется бар, а затем я хочу, чтобы 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 достаточно умен не включать ссылку в этом случае.

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

Ответы [ 3 ]

26 голосов
/ 20 февраля 2011

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

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) конструктором хотя это поле никогда больше не используется.

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

5 голосов
/ 30 января 2014

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

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());
    }
}
1 голос
/ 18 декабря 2013

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

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

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

...