Javaの開発を現役で5年ほどやって来ましたが、1つ勘違いしていたことがありました。
Javaでは循環参照してもメモリリークは発生しないのです。
すごく今更ですね。
確かに、Javaのメモリ解放は リファレンスカウンタ式ではない ため、前々から循環参照しないのでは?と思っていましたが、一応循環参照する部分はWeakリファレンスにしたり、Androidで言うなら破棄するタイミングでnullを入れたりしていました。
JavaのGCは ルートからたどっていってたどり着かなかったものを開放する ということは常に頭に入っていたので、循環参照しないのでは?と思いつつも、実際に答えを出さないままでした。
今更で当たり前かもしれないですが、実はこの事実を知らない人はいるのでは…?
実験
public class A {
public final String message;
public A ref;
public A(String message) {
this.message = message;
}
@Override
protected void finalize() throws Throwable {
System.out.println(message);
super.finalize();
}
}
public class Main {
public static void execute() {
A a1 = new A("a1 release.");
A a2 = new A("a2 release.");
a1.ref = a2;
a2.ref = a1;
}
public static void main(String[] args) {
execute();
System.gc();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
}
}
実行結果は、
a2 release.
a1 release.
でした。
結果、循環参照していてもきちんと開放される。
finalize() はオブジェクトがGCの対象となった時にコールされます。(その前にプロセスが終了した等の場合は呼び出されないようです)
finalize() がコールされ、その後のGCで開放されます。
つまり、ちゃんとGCの "対象" にされたということです。
GCのコストを考えれば、やはりこの程度のメリットもあるわけですね。
ということは(ネイティブを除き)Javaでメモリリークに気をつけないといけない場面というのは、不要なオブジェクトを「いつまでも保持しない」という点だけでしょうか?
いつかは正しく開放されるけど、不要なオブジェクトを参照しているとその不要なものは開放されない = 一応、メモリリーク
安全な言語ですね。
でも、GCのコストはやはり高い。