大きなオブジェクト
上記でガベージコレクションについて解説しましたが、実際には非常に大きなオブジェクトの場合は異なった管理のされかたをします。
こういった大きなオブジェクトのことをLarge Object Heap(LOH)と言い具体的には85000バイト以上のサイズのオブジェクトがLOHとして扱われます。
配列であればおよそ2万エントリ以上のものがLOHになると思われます。
ガベージコレクションとLOH
非常に大きなオブジェクトがヒープにある場合にガベージコレクションでメモリのコンパクションが発生するとこのオブジェクトの移動に
時間がかかってしまい許容できないパフォーマンスの低下が発生します。.NETのガベージコレクションはこの問題を解決するためにLOHに関しては
メモリのコンパクション時にメモリの移動を行いません。こうすることでガベージコレクションのパフォーマンスの低下を防ぎます。
メモリの断片化
しかしながらLOHの仕組みによって.NET4.0以前では新たにメモリの断片化という問題が発生します。以下のような場合を考えて見ます。
下図のように85Kのオブジェクトと10MBのオブジェクトとがメモリを使用しているとします。
ガベージコレクションが発生し10MBのオブジェクトがクリアされたとします。このとき85KのオブジェクトはLOHなのでメモリの移動が行われず下図のようになります。
新たに11MBのオブジェクトを配置しようとしたときに10MBのスペースはもはや使用できません。Next obj Ptrは10MBのブロックをスキップします。
ヒープの末尾に11MBのオブジェクトが配置されることになります。
さらに85KのLOHが配置されると以下のようになります。
このあと11MBのメモリがクリアされて同様のことが繰り返されるとメモリの断片化が発生し利用可能なメモリのスペースがあるにもかかわらずOutOfMemoryExceptionが
発生するということになってしまいます。
実際に発生する状況の例
上のセクションで示したのは最悪のケースですが実際にそんなことが起こりえるのでしょうか?いくつかの例を挙げてみたいと思います。
List<T>に項目を追加するという以下のコードを見てみます。
List<Int32> l = new List<Int32>();
l.Add(100);
ウォッチウィンドウで変数 l の内部を見ると以下のようになっています。
内部に配列がありそこに各要素の値が入っていることがわかります。要素の数を5個まで増やしてみます。
List<Int32> l = new List<Int32>();
l.Add(100);
l.Add(90);
l.Add(80);
l.Add(70);
l.Add(60);
内部の配列が要素数8個の配列で置き換えられているのがわかります。今まで使用されていた要素数4個の古い配列はガベージコレクションの対象となります。
もし多くの要素を持つリスト(20000個くらい)に要素を追加していった場合、メモリの断片化が発生する可能性がでてきます。
解決策 (.NET4.0以前)
LOHの問題は非常に回避が難しいように思えますが解決策はあります。まずは85K以上のサイズのファイルやデータをなるべく使用しないようにするというのが大事です。
どうしても使用しなければならない場合、例えばそのデータはByteの配列などで持つことになると思います。配列のサイズが10MBの場合はLOHとして扱われてしまいます。
それを防ぐためにIListを実装したカスタムのリストクラスを作成します。
そのクラスの内部では配列を小分けにして複数の小さな配列でデータを持たせることによりLOHの作成を抑制できます。LOHが作成されなければメモリの断片化も発生しません。
またこのクラスを利用して既存のコードをLOHが発生しないクラスで透過的に置き換えることが可能です。
要件によってはもっとシンプルな方法で解決することもできます。必要十分な配列を確保し、使用し終わったら全て解放するという方法です。
System.Collections名前空間の各クラスにはCapacityプロパティがありコンストラクタで配列の最大サイズを指定することができます。
LOHの発生は避けられませんがサイズが徐々に増加する場合に比べメモリの断片化は少なくなりOutOfMemoryExceptionの発生の確率を下げることが可能です。
解決策 (.NET4.5以降)
.NET4.5以降ではガベージコレクションが改良されています。断片化によって生じたメモリブロックのリストを内部的に管理しており再利用がされるようになっています。
これによって断片化が発生してもいずれはそのメモリブロックが再利用されることとなりパフォーマンスが大幅に改善されています。
しかしながら依然としてLOHが発生しないようにすることは重要です。