3

そこで、私は遺伝的アルゴリズム(Javaでコード化)の並列化に取り組んでおり、Executorを使用して、母集団内の個人のフィットネステストの非同期実行を管理することにしました。これを行ったのは、スレッドプールサイズが固定されたエグゼキュータを作成し、世代ごとに新しいスレッドを作成するのではなく、世代ごとにそれらのスレッドを再利用できることを意味します。

現在、人口規模の増加に伴うGAのパフォーマンスを監視するために実行している一連のテストがあり、問題が発生しました。次のコードを実行します。

        for(i=1;i<=11; i++){
            PopulationSize = 10*i;
            for(j=0;j<10;j++){

            startTime = System.nanoTime();

            P = new Population(PopulationSize, crossOverProbability, mutationProbability, conGens);         

            while(P.generation()<10){
                P.breedNewPop();    
            }

            endTime = System.nanoTime();
            time = (endTime - startTime) * Math.pow(10, -9);

            System.out.println("Done Trial " + i + ", Round " + j);
            }
        }

次のエラーが発生します:

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

私にとって紛らわしいのは、これがトライアル10、ラウンド4で発生していることです。つまり、トライアル10の最初の3ラウンドを問題なく実行できたということです。ラウンド4を実行しても違いはないはずなので(特に、ラウンド4はトライアル10のラウンド1〜3より多くのスレッドを必要としません)、問題が発生することはないと思います。しかし、そうです。

私が今持っている理論の1つは、Javaが適切なガベージコレクションを行っていないということです。つまり、何らかの理由で、古い未使用のスレッドがクリアされていないため、このような特殊な瞬間にメモリが不足します。これだと思って、単に割り当てるのではなく、ループ内でPを宣言して割り当てることを試みました。それは効果がありませんでした。また、ループの最後に追加P = null; System.gc();して、新しいスレッドプールが作成される前にガベージコレクションを強制しようとしました。繰り返しますが、違いはありませんでした。

エグゼキュータを処理する関連するコード行は次のとおりです。

Population()の場合:executor = Executors.newFixedThreadPool(popSize);

Population.findFitness()の場合:

for(int i=0; i<individuals.length; i++){
        executor.execute(individuals[i]);
    }try {
        cdl.await();
    } catch (InterruptedException e) {
        System.out.println("Error: Thread interrupted.");
    }

(私はCountDownLatchを使用して、すべてのスレッドの実行が完了するのを待ちます-エグゼキューターを介してスレッドプールを使用する代わりに、各個人のフィットネステストを独自のスレッドに配置することで並列化していたときからすでに実装しています。ラッチは、ExecutorServiceのinvokeAll()メソッドのようなものよりも、Individualの実装に適しているようにも見えました。)

Individual.run()のコード:

public void run(){
    try{
        findFitness();
    }catch (Exception e){ 
        System.out.println("Error in Individual.run(): " + e.getMessage());
    }finally{
        stopLatch.countDown();
    }
}

この時点で、私はそれを引き起こしている可能性があるものについて途方に暮れています。なぜこれが起こるのか、そしてどうすればそれを修正できるのか、誰かが何か考えを持っていますか?

PSより多くのメモリを使用してJVMを実行してみることができることはわかっていますが、それでもエラーの固有のタイミングを説明することはできません。このプログラムをあるマシンでプログラミングしていて、最終的には別のマシンに移動することを考えると、比較的力ずくで修正するのではなく、エラーの背後にある理由を理解したいと思います。

更新:試行を繰り返して実行しましたが、今回はJConsoleを介してスレッドを監視しているので、エグゼキュータが適切なサイズのスレッドプールを作成していることを確認できます。ただし、スレッドプールは破棄されていません。テストのラウンドごとに(つまり、jをカウントするforループを通過するたびに)、新しいスレッドプールが生成されますが、古いスレッドプールは残ります。なぜこれが起こるのでしょうか?

4

2 に答える 2

3

固定サイズのスレッド プールでスレッドを作成するときにメモリが不足するというのは、非常に奇妙に思えます。次のいずれかが疑われます。

  • スレッドプールは実際には固定サイズではありません。つまり、プール作成パラメーターが間違っています。
  • あなたのコードは別の場所にスレッドを作成しています。new Thread().start()たとえば、明示的に呼び出します。これは、スタック トレースに表示される場合があります。

もう 1 つの可能性は、JVM の外部にある何かが原因で、JVM がスレッド スタックを割り当てられないことです。これらは通常のヒープ メモリに割り当てられないため、-Xmx の設定にはなりません。これは、デフォルトのスレッド スタック サイズ設定である可能性があります。または、外部リソースの制限である可能性があります...またはマシンの一般的なリソース不足である可能性があります。


この例外メッセージ:

Exception in thread "main" java.lang.OutOfMemoryError: 
     unable to create new native thread .

これは明らかに、GC によって検出される通常の「ヒープがいっぱいです」タイプの問題ではありません。失敗したメモリ割り当ては、スレッド スタック用の非ヒープ メモリの要求です。ヒープ サイズを大きくしても効果はありません。さらに悪化する可能性もあります。

GC を強制的に実行しても効果はありません。また、問題がヒープ オブジェクトの割り当てによって引き起こされた場合でも、問題は解決しません。JVM は GC の実行後にのみヒープ OOME をスローするためです。

于 2012-06-16T06:35:51.703 に答える
0

コメントがたくさん出てくるので、これを「答え」にします。

あなたが欲しいのはThreadPoolExecutorだと思います。

join実際、基本に戻って一連のThreadインスタンスを起動し、メソッドを繰り返し使用して、すべてがいつ終了するかを確認する方が簡単だと思うかもしれません。適切なスレッドプールを使用すると、2コアマシンで同時に数百のスレッドを実行できなくなりますが、経験から、Javaはプールを必要とせずに数千のスレッドをまっすぐに保つことができます。(私がコーディングする方法では、ほとんどのスレッドがロックを待って相互に通信しているため、すべてがフラットに実行されているわけではありません。しかし、多くのスレッドはフラット実行されており、CPUを詰まらせません。)レートを設定し、すべてのスレッドを実行してから、ある種のプールを試してください。

Javaは現在、マルチスレッドをより簡単かつ優れたものにするためにあらゆる種類のクラスを提供していますが、特に修士論文を書くのではなくプログラムを機能させようとしている場合は、それらが実際に何をするのかが常に明確であるとは限りません。

于 2012-06-19T01:39:55.263 に答える