Upload page content

You can upload content for the page named below. If you change the page name, you can also upload content for another page. If the page name is empty, we derive the page name from the file name.

File to load page content from
Page name
Comment

Locked History Actions

java/Finalizer

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で表示してみると、以下のような状態が観察される。

Finalizer.png

つまり、ヒープ領域のほとんどをjava.lang.ref.Finalizer(とそこから参照されるオブジェクト)に埋め尽くされてしまっている。

また、Dominator Treeを見てみると状況によっては、Finalizerが入れ子になっている場合がある。

Finalizer2.png

次のようにフル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();
    }
  }
}