2009-04-24 11 views
5

この簡単なサンプルコードは、この問題を示しています。 ArrayBlockingQueueを作成し、take()を使用してこのキューのデータを待機するスレッドを作成します。ループが終了すると、理論的にはキューとスレッドの両方がガベージコレクトされますが、実際にはすぐにOutOfMemoryErrorが返されます。これをGCにすることを妨げているのは何ですか?これをどのように修正できますか?OutOfMemoryError - 待機中のスレッドがガベージコレクトされないのはなぜですか?

/** 
* Produces out of memory exception because the thread cannot be garbage 
* collected. 
*/ 
@Test 
public void checkLeak() { 
    int count = 0; 
    while (true) { 

     // just a simple demo, not useful code. 
     final ArrayBlockingQueue<Integer> abq = new ArrayBlockingQueue<Integer>(2); 
     final Thread t = new Thread(new Runnable() { 
      @Override 
      public void run() { 
       try { 
        abq.take(); 
       } catch (final InterruptedException e) { 
        e.printStackTrace(); 
       } 
      } 
     }); 
     t.start(); 

     // perform a GC once in a while 
     if (++count % 1000 == 0) { 
      System.out.println("gc"); 
      // this should remove all the previously created queues and threads 
      // but it does not 
      System.gc(); 
     } 
    } 
} 

私はJava 1.6.0を使用しています。

UPDATE:数回反復してGCを実行しますが、これは役に立ちません。

答えて

8

スレッドはトップレベルのオブジェクトです。それらは「特別な」ものなので、他のオブジェクトと同じ規則に従わない。彼らは「生きている」(すなわち、GCから安全である)ための参照に依存していません。スレッドは、終了するまでガベージコレクションを行いません。スレッドがブロックされているため、サンプルコードでは発生しません。もちろん、スレッドオブジェクトがガベージコレクションされていないので、それによって参照される他のオブジェクト(ケースのキュー)もガベージコレクションできません。

0

スレッドを開始すると、新しいスレッドはすべて非同期で実行され、ループは新しいスレッドを作成し続けます。

コードがロックされているため、スレッドはシステム内のライフリファレンスであり、収集できません。しかし、何らかの作業を行っていても、スレッドは作成されたときほど早く終了する可能性は低いので(少なくともこのサンプルでは)、GCはすべてのメモリを収集することができず、OutOfMemoryExceptionで失敗します。

多くのスレッドを作成することは効率的でも効率的でもありません。保留中の操作をすべて並列に実行する必要がない場合は、スレッドプールと実行可能キューを使用して処理することができます。

+0

スレッドごとにSystem.gc()を実行しても、スレッドは破棄されません。 – martinus

+1

@martinusもっと慎重に読んでください。スレッドのガベージコレクションにかかる時間は、スレッドの作成に要する時間よりも長くなります。あなたが止められていないバスタブを満たすのを開始し、あなたが水を浴槽の排水口よりも速く加えると、浴槽は最終的に満杯になりオーバーフローします。これは、リソースの割り当て/解放の競合状態です。 100スレッドしか作成せずにwhileループを続けるとどうなりますか?スレッドは最終的に収集されますか? – Wedge

+0

@wedge、いいえ、彼らは収集されていません!または何か他のことをする。各ループの終わりに1秒待っても、GC'dは何もありません。 – martinus

5

スレッドはすべて、ArrayBlockingQueue<Integer> abqにエントリがあるまでブロックされているため、無期限に作成しています。したがって、最終的にOutOfMemoryErrorが表示されます。

(編集)

作成した各スレッドには、1つのエントリとしてabqキューまでのでブロックを終了することはありません。 スレッドが実行されている場合、GCはスレッドabqとスレッド自体を含め、スレッドが参照しているオブジェクトを収集しません。

+0

はい、ループが終了してもキューは参照されません – martinus

+1

ループが終了した理由は何ですか?ループは永遠に続きます... –

+0

ループの終わりが再び先頭から始まり、以前に作成されたキューとスレッドはもう参照されません。 – martinus

0

whileループは無限ループであり、新しいスレッドを連続的に作成します。作成したスレッドの実行をすぐに開始しますが、スレッドによってタスクを完了するのにかかる時間は、スレッドの作成に要する時間よりも長くなります。

また、whileループ内でabqパラメータを宣言することで、何をしていますか?

編集内容やその他のコメントに基づいています。 System.gc()はGCサイクルを保証しません。スレッドの実行速度を上回る私のステートメントを読むことは、作成速度よりも遅いです。

take()メソッドのコメントを確認しました。 "このキューの先頭を取得して削除し、このキューに要素がない場合は待機します。"私はあなたがArrayBlockingQueueを定義しているのを見ていますが、要素を追加していないので、すべてのスレッドがそのメソッドを待っているだけです。つまりOOMを取得しています。

2
abq.put(0); 

は1日を節約します。

スレッドはすべてキューのtake()で待機しますが、それらのキューには何も入れません。

+0

これは明らかに正解です! –

0

Javaでスレッドがどのように実装されているのかわかりませんが、キューとスレッドが収集されない理由が考えられます。スレッドはシステム同期プリミティブを使用するシステムスレッド用のラッパーです。スレッドが生存しているかどうかを知ることができないため、待機中のスレッドを自動的に収集します。すなわち、GCはスレッドが起動できないことを単に知りません。

私はあなたが何をしようとしているのか知っている必要があるので、それを修正するには何が最善の方法だと言うことはできませんが、java.util.concurrentを見て何をするクラスあなたが必要です。

0

System.gcコールは何もしないので、何もしません。スレッドが起動すると、スレッド参照カウントがインクリメントされます。そうしないと、スレッドが不定期に終了することになります。スレッドのrunメソッドが完了すると、スレッドの参照カウントがデクリメントされます。メインループが終了すると、スレッドtは任意の処理を行う機会を持つ前に、ガベージコレクションを行う場合、すなわち、それはabq.take前に、OSによって中断されているもの -

while (true) { 
    // just a simple demo, not useful code. 
    // 0 0 - the first number is thread reference count, the second is abq ref count 
    final ArrayBlockingQueue<Integer> abq = new ArrayBlockingQueue<Integer>(2); 
    // 0 1 
    final Thread t = new Thread(new Runnable() { 
     @Override 
     public void run() { 
      try { 
       abq.take(); 
       // 2 2 
      } catch (final InterruptedException e) { 
       e.printStackTrace(); 
      } 
     } 
    }); 
    // 1 1 
    t.start(); 
    // 2 2 (because the run calls abq.take) 
    // after end of loop 
    // 1 1 - each created object's reference count is decreased 
} 

は今、潜在的な競合状態がありますステートメントが実行されますか? runメソッドは、GCが解放した後にabqオブジェクトにアクセスしようとしますが、これは悪いことです。

競合状態を回避するには、オブジェクトをパラメータとしてrunメソッドに渡す必要があります。最近はJavaについてはわかりませんが、しばらく経っていますので、オブジェクトをコンストラクタパラメータとしてRunnableから派生したクラスに渡すことをお勧めします。こうすることで、runメソッドが呼び出される前にabqへの参照が追加され、オブジェクトが常に有効であることが保証されます。

+0

JavaはGCの参照カウントを使用しません。 – TrayMan

+0

クイック検索では、参照カウントがないことを確認します。 「参照カウント」を「オブジェクトへの参照数」に置き換えます。オブジェクトが使用するメモリは、そのオブジェクトへの参照がない場合にのみ解放されます。サンプルコードには、OPが実現していないことが記載されています。 – Skizz

+0

参照カウントであっても、 't.start()'呼び出しが 't'の参照カウントを増加させることで、競合状態を簡単に防ぐことができます。今、 't'がスコープから外れると、まだ1つの参照がカウントされているので、' t'でGCは発生しません。 Javaは参照カウントを使用しませんが、基本的な考え方は同じです:新しいスレッド(スレッド)が呼び出されたときだけでなく、 'start()'メソッドが呼び出されるとすぐにスレッドは実行中とマークされ実行を開始する。 –

関連する問題