そこで、私は遺伝的アルゴリズム(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ループを通過するたびに)、新しいスレッドプールが生成されますが、古いスレッドプールは残ります。なぜこれが起こるのでしょうか?