## page was renamed from java/Finalier
= FinalizerとフルGC =
== 問題と解決 ==
次のコードは(無茶ではあるが)、きちんと動作する。
{{{
/* 1 */
public class GCTest {
byte[]a = new byte[3000000];
public static void main(String[]args) {
while (true) {
new Thread() {
public void run() {
GCTest test = new GCTest();
System.out.println("CREATED!!!");
}
}.start();
}
}
}
}}}
ここに、以下のようにfinalizeを追加すると、各スレッド内でOutofMemoryErrorが発生してGCTestオブジェクトは作成されなくなる。
{{{
/* 2 */
public class GCTest {
byte[]a = new byte[3000000];
@Override protected void finalize() throws Throwable { //!!!
super.finalize(); //!!!
} //!!!
public static void main(String[]args) {
while (true) {
new Thread() {
public void run() {
GCTest test = new GCTest();
System.out.println("CREATED");
}
}.start();
}
}
}
}}}
このとき、ヒープダンプをEclipse Memory Analyzerで表示してみると、以下のような状態が観察される。
{{attachment:Finalizer.png}}
つまり、ヒープ領域のほとんどをjava.lang.ref.Finalizer(とそこから参照されるオブジェクト)に埋め尽くされてしまっている。
また、Dominator Treeを見てみると状況によっては、Finalizerが入れ子になっている場合がある。
{{attachment:Finalizer2.png}}
次のようにフルGCを強制することで解決できる(ただし、必ずしもフルGCが起こるとは限らない。System.gc()はあくまでもヒント)。
※System.gc()の代わりにSystem.runFinalization()でもうまくいくようである。
うまくいかない。フルGCを行う以外に方法がないと思われる
{{{
/* 3 */
public class GCTest {
byte[]a = new byte[3000000];
@Override protected void finalize() throws Throwable { //!!!
super.finalize(); //!!!
} //!!!
public static void main(String[]args) {
while (true) {
new Thread() {
public void run() {
GCTest test = new GCTest();
System.out.println("CREATED");
}
}.start();
System.gc(); //!!!
}
}
}
}}}
あるいは、(時間的に余裕があるならば)以下のようにしてもよい。
{{{
/* 4 */
public class GCTest {
byte[]a = new byte[3000000];
@Override protected void finalize() throws Throwable { //!!!
super.finalize(); //!!!
} //!!!
public static void main(String[]args) {
new Thread() { //!!!
public void run() { //!!!
try { //!!!
Thread.sleep(1000); //!!!
} catch (Exception ex) { //!!!
} //!!!
System.gc(); //!!!
} //!!!
}.start(); //!!!
while (true) {
new Thread() {
public void run() {
GCTest test = new GCTest();
System.out.println("CREATED");
}
}.start();
}
}
}
}}}
GCの挙動を観察してみると、/* 2 */のコードでも自動的にフルGCは行われているが、使用ヒープ領域は減少していない。Finalizerで埋め尽くされてしまった後にフルGCを行っても意味が無いようである。
finalize()を実装したオブジェクトを使用する場合(特に複数のスレッドで生成する場合)には、明示的にフルGCを指示しなければならない模様。ヒープ領域が満杯になった際の自動フルGCでは回収されないようである。
== 参考資料 ==
* [[http://www.atmarkit.co.jp/fjava/rensai4/troublehacks09/troublehacks09_3.html|JavaのGC頻度に惑わされた年末年始の苦いメモリ]]
* [[http://www.devx.com/Java/Article/30192|How to Handle Java Finalization's Memory-Retention Issues]]
* [[http://java.sun.com/developer/technicalArticles/javase/finalization/|How to Handle Java Finalization's Memory-Retention Issues(上記と同じ内容)]]
* [[http://japan.internet.com/column/developer/20060404/26.html|Javaファイナライズのメモリ保持問題への対処方法(上記の翻訳)]]
* [[http://gceclub.sun.com.cn/java_one_online/2005/TS-3281/|Finalization, Threads, and the Java Technology-Based Memory Model]]
== RMIを使用している場合 ==
RMIを使用する場合は、デフォルトで1分間に一度フルGCが指示されているようである。
したがって、RMIを使用するシステムと、使用していないシステムでは、それ以外の部分で同じ処理を行っていても挙動が異なる。
== フルGCによって弱参照オブジェクトはどうなるか? ==
SoftReferenceは保持される。
{{{
public class GCTest {
private static SoftReference