Deletions are marked like this. | Additions are marked like this. |
Line 42: | Line 42: |
<img src="/files/Finalizer.png"> | {{attachment:Finalizer.png}} |
Line 48: | Line 48: |
<img src="/files/Finalizer2.png"> | {{attachment:Finalizer2.png}} |
Line 112: | Line 112: |
<a href="http://www.atmarkit.co.jp/fjava/rensai4/troublehacks09/troublehacks09_3.html">JavaのGC頻度に惑わされた年末年始の苦いメモリ</a> <a href="http://www.devx.com/Java/Article/30192">How to Handle Java Finalization's Memory-Retention Issues</a> <a href="http://java.sun.com/developer/technicalArticles/javase/finalization/">How to Handle Java Finalization's Memory-Retention Issues</a> <a href="http://gceclub.sun.com.cn/java_one_online/2005/TS-3281/">Finalization, Threads, and the Java Technology-Based Memory Model</a> |
* [[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://gceclub.sun.com.cn/java_one_online/2005/TS-3281/|Finalization, Threads, and the Java Technology-Based Memory Model]] |
問題と解決
次のコードは(無茶ではあるが)、きちんと動作する。
/* 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で表示してみると、以下のような状態が観察される。
つまり、ヒープ領域のほとんどをjava.lang.ref.Finalizer(とそこから参照されるオブジェクト)に埋め尽くされてしまっている。
また、Dominator Treeを見てみると状況によっては、Finalizerが入れ子になっている場合がある。
次のようにフルGCを強制することで解決できる(ただし、必ずしもフルGCが起こるとは限らない。System.gc()はあくまでもヒント)。
※<s>System.gc()の代わりにSystem.runFinalization()でもうまくいくようである。</s> <font color="red">うまくいかない。フルGCを行う以外に方法がないと思われる</font>
/* 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では回収されないようである。
参考資料
RMIを使用している場合
RMIを使用する場合は、デフォルトで1分間に一度フルGCが指示されているようである。 したがって、RMIを使用するシステムと、使用していないシステムでは、それ以外の部分で同じ処理を行っていても挙動が異なる。
フルGCによって弱参照オブジェクトはどうなるか?
SoftReferenceは保持される。
public class GCTest { private static SoftReference<Object> ref; public static void main(String[]args) { ref = new SoftReference<Object>(new Object()); while (true) { if (ref.get() == null) { ref = new SoftReference<Object>(new Object()); System.out.println("Creating"); } else { System.out.println("Existing"); } System.gc(); } } }
WeakReferenceは破棄される。
public class GCTest { private static WeakReference<Object> ref; public static void main(String[]args) { ref = new WeakReference<Object>(new Object()); while (true) { if (ref.get() == null) { ref = new WeakReference<Object>(new Object()); System.out.println("Creating"); } else { System.out.println("Existing"); } System.gc(); } } }