web-dev-qa-db-ja.com

C#:メモリ不足例外

今日、私のアプリケーションはOutOfMemoryExceptionを投げました。 4GB RAMと多くの仮想メモリもあるので、これは常に不可能でした。既存のコレクションを新しいリストに追加しようとしたときにエラーが発生しました。

List<Vehicle> vList = new List<Vehicle>(selectedVehicles);  

私の新しいリストに含まれるはずの車両がメモリ内に既に存在するため、ここではメモリはあまり割り当てられていません。 Vehicleは非常に複雑なクラスであり、一度に約50.000のアイテムを新しいリストに追加しようとしたことを認めなければなりません。しかし、アプリケーション内のすべてのVehicleは200 MBのサイズのデータ​​ベースに由来するため、この時点でOutOfMemoryExceptionが発生する原因はわかりません。

63
TalkingCode

2つのポイント:

  1. 32ビットのWindowsを実行している場合、4GBのすべてにアクセスできるわけではなく、2GBしかありません。
  2. Listの基礎となる実装は配列であることを忘れないでください。メモリが非常に断片化されている場合、合計で十分な空きメモリがあるにもかかわらず、Listを割り当てるのに十分な連続したスペースがない場合があります。
72
Tudor

3年前のトピックですが、別の実用的なソリューションが見つかりました。十分な空きメモリがあり、64ビットOSを実行しているにもかかわらず例外が発生する場合は、Project properties-> Buildタブに移動し、x64Platform targetとして設定してください。

enter image description here

。Net4.5には、オブジェクトに対する2GBの制限がなくなりました。この行をApp.configに追加します

<runtime>
    <gcAllowVeryLargeObjects enabled="true" />    
</runtime>

OutOfMemoryExceptionを取得せずに非常に大きなオブジェクトを作成することが可能になります

X64 OSでのみ機能することに注意してください!

67
Lendmann

アプリケーションのメモリと比較してデータベースに保存されるデータは非常に異なります。

オブジェクトの正確なサイズを取得する方法はありませんが、これを行うことができます。

GC.GetTotalMemory() 

一定量のオブジェクトがロードされた後、リストをロードするときにメモリがどれだけ変化しているかを確認します。

過剰なメモリ使用を引き起こしているのがリストである場合、それを最小化する方法を検討できます。なぜ50,000個のオブジェクトを最初から一度にすべてメモリにロードするのかなど。必要に応じてDBを呼び出すのが最善ではないでしょうか?

ここを見てみると、 http://www.dotnetperls.com/array-memory また、.NETのオブジェクトが実際のデータよりも大きいことがわかります。汎用リストは、配列よりもさらにメモリを消費します。オブジェクト内に汎用リストがある場合は、さらに速く成長します。

13
Adam Pedley

(32ビットマシンの)OutOfMemoryExceptionは、メモリの実際のハード制限と同じくらい頻繁にフラグメンテーションに関するものです-これについては多くを見つけることができますが、ここで簡単に議論する最初のGoogleヒットがあります: http://blogs.msdn .com/b/joshwil/archive/2005/08/10/450202.aspx 。 (@Anthony Pegramは、上記のコメントで同じ問題に言及しています)。

とは言っても、上記のコードにはもう1つ思い浮かぶ可能性があります。リストに「IEnumerable」コンストラクタを使用しているため、mayリストコンストラクターに渡すコレクションのサイズに関するヒントをオブジェクトに与えない。渡すオブジェクトがコレクションではない場合(ICollectionインターフェースを実装しない場合)、背後でList実装は数回(または数回)成長する必要があり、そのたびに置き去りになります。ガベージコレクションが必要な小さすぎる配列。ガベージコレクターは、おそらく破棄された配列に十分な速さで到達できず、エラーが発生します。

これに対する最も簡単な修正は、List(int capacity)コンストラクターを使用して、割り当てるバッキング配列サイズをフレームワークに伝えることです(たとえば、 "50000"を推測して推測する場合でも)、AddRange(IEnumerable collection)実際にリストを作成するメソッド。

だから、私が正しいなら最も簡単な「修正」:交換

List<Vehicle> vList = new List<Vehicle>(selectedVehicles);

List<Vehicle> vList = new List<Vehicle>(50000);  
vList.AddRange(selectedVehicles);

他のすべてのコメントと回答は、全体的な設計決定の観点から依然として適用されますが、これはmightである可能性があります。

注(@Alexが下でコメントしたように)、これはselectedVehiclesがICollectionではない場合にのみ問題になります。

8
Tao

私の開発チームはこの状況を解決しました:

次のポストビルドスクリプトを.exeプロジェクトに追加してコンパイルし直し、ターゲットをx86に設定し、1.5 GB増加し、x64プラットフォームターゲットも3.2 GBを使用してメモリを増加させました。アプリケーションは32ビットです。

関連URL:

脚本:

if exist "$(DevEnvDir)..\tools\vsvars32.bat" (
    call "$(DevEnvDir)..\tools\vsvars32.bat"
    editbin /largeaddressaware "$(TargetPath)"
)
5
Mehmet Kurt

すべてのリストを一度に持ち込もうとしないでください。データベース内の要素のサイズは、メモリに取り込むものと同じではありません。要素を処理する場合は、for eachループを使用し、エンティティフレームワークの遅延読み込みを利用して、すべての要素を一度にメモリに入れないようにする必要があります。リストを表示したい場合は、ページネーション(.Skip()および.take())を使用します

3
Sr.PEDRO

GCはメモリホールを排除する最適化戦略の一環として小さなオブジェクトヒープを圧縮しますが、パフォーマンス上の理由からGCは大きなオブジェクトヒープを圧縮しません** )**。したがって、x86システムで多数の大きなオブジェクトを使用するプログラムを実行している場合、OutOfMemory例外が発生する可能性があります。そのプログラムをx64システムで実行している場合、断片化されたヒープがある可能性があります。

3
Ali Bayat

私はこれが古い質問であることを知っていますが、答えのどれも大きなオブジェクトヒープについて言及していないので、これはこの質問を見つける他の人に役立つかもしれません...

85,000バイトを超える.NETのメモリ割り当ては、通常の小さなオブジェクトヒープではなく、大きなオブジェクトヒープ(LOH)から発生します。なぜこれが重要なのですか?大きいオブジェクトヒープは圧縮されないためです。これは、大きなオブジェクトヒープが断片化されることを意味し、私の経験では、これは必然的にメモリ不足エラーにつながります。

元の質問では、リストには50,000個のアイテムが含まれています。内部的にリストは配列を使用し、32ビットで50,000 x 4バイト= 200,000バイト(または64ビットの場合は2倍)が必要であると想定しています。そのため、メモリ割り当てはラージオブジェクトヒープから発生します。

それであなたはそれについて何ができますか?

4.5.1より前の.netバージョンを使用している場合、できることは、問題を認識して回避することです。したがって、この例では、車両のリストを持つ代わりに、車両のリストのリストを持つことができます。ただし、リストに含まれる要素が18,000を超えることはありません。それはsomeいコードにつながる可能性がありますが、実行可能な回避策です。

.net 4.5.1以降を使用している場合、ガベージコレクターの動作は微妙に変更されています。大量のメモリ割り当てを行う場所に次の行を追加する場合:

System.Runtime.GCSettings.LargeObjectHeapCompactionMode = System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce;

ガベージコレクターに大きなオブジェクトヒープの圧縮を強制します-次回のみ。

それは最良の解決策ではないかもしれませんが、以下は私のために働いています:

int tries = 0;
while (tries++ < 2)
{
  try 
  {
    . . some large allocation . .
    return;
  }
  catch (System.OutOfMemoryException)
  {
    System.Runtime.GCSettings.LargeObjectHeapCompactionMode = System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce;
    GC.Collect();
  }
}

もちろん、これは物理(または仮想)メモリが使用可能な場合にのみ役立ちます。

1
Brian Cryer

.Netが進歩するにつれて、誰もがトリップする新しい32ビット構成を追加できるようになります。

.Net Framework 4.7.2を使用している場合は、次の手順を実行します。

プロジェクトのプロパティに移動

構築する

「prefer 32-bit」のチェックを外します

乾杯!

1
user2326106