Разве анонимные классы * всегда * поддерживают ссылку на их прилагаемый экземпляр?
Я работаю с некоторым кодом, где один объект, "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 достаточно умен
чтобы не включать ссылку в этом случае.
Итак... экземпляр анонимного внутреннего класса удерживает
ссылку на свой охватывающий экземпляр, даже если он никогда не использует
ссылка на экземпляр экземпляра?
Ответы
Ответ 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
, которые передаются в этом случае, содержат ссылку на внешний экземпляр (если это не требуется).