ASP.NET Core のメモリ管理とガベージコレクション (GC)Memory management and garbage collection (GC) in ASP.NET Core

Sébastien RosRick AndersonBy Sébastien Ros and Rick Anderson

メモリ管理は、.NET などのマネージフレームワークでも複雑です。Memory management is complex, even in a managed framework like .NET. メモリの問題を分析して理解することは困難な場合があります。Analyzing and understanding memory issues can be challenging. この記事の内容:This article:

  • 多くの メモリリークが発生 し、GC が動作して いない 問題が発生しました。Was motivated by many memory leak and GC not working issues. これらの問題のほとんどは、.NET Core でのメモリ消費のしくみを理解していないか、測定方法を理解していないことによって発生しました。Most of these issues were caused by not understanding how memory consumption works in .NET Core, or not understanding how it's measured.
  • 問題のあるメモリ使用方法を示し、別のアプローチを提案します。Demonstrates problematic memory use, and suggests alternative approaches.

.NET Core でのガベージコレクション (GC) のしくみHow garbage collection (GC) works in .NET Core

GC は、各セグメントが連続するメモリ範囲であるヒープセグメントを割り当てます。The GC allocates heap segments where each segment is a contiguous range of memory. ヒープに配置されたオブジェクトは、0、1、または2の3つのジェネレーションに分類されます。Objects placed in the heap are categorized into one of 3 generations: 0, 1, or 2. 生成によって、アプリケーションによって参照されなくなったマネージオブジェクトのメモリを GC が解放する頻度が決まります。The generation determines the frequency the GC attempts to release memory on managed objects that are no longer referenced by the app. 下位の番号付きジェネレーションは、GC の方が頻繁に行われます。Lower numbered generations are GC'd more frequently.

オブジェクトは、有効期間に基づいて、ある世代から別の世代に移動されます。Objects are moved from one generation to another based on their lifetime. オブジェクトが長くなると、オブジェクトは上位世代に移動されます。As objects live longer, they are moved into a higher generation. 既に説明したように、より高い世代は GC の方が頻繁に発生します。As mentioned previously, higher generations are GC'd less often. 短期有効期間オブジェクトは常にジェネレーション0に残ります。Short term lived objects always remain in generation 0. たとえば、web 要求の有効期間中に参照されるオブジェクトは、短時間で終了します。For example, objects that are referenced during the life of a web request are short lived. 一般に、アプリケーションレベル シングルトン は第2世代に移行します。Application level singletons generally migrate to generation 2.

ASP.NET Core アプリが開始されると、GC は次のようになります。When an ASP.NET Core app starts, the GC:

  • 初期ヒープセグメント用にメモリを予約します。Reserves some memory for the initial heap segments.
  • ランタイムが読み込まれるときに、メモリの一部をコミットします。Commits a small portion of memory when the runtime is loaded.

前のメモリ割り当ては、パフォーマンス上の理由から実行されます。The preceding memory allocations are done for performance reasons. パフォーマンス上の利点は、連続したメモリのヒープセグメントから取得されます。The performance benefit comes from heap segments in contiguous memory.

GC を呼び出します。収集Call GC.Collect

GC を呼び出しています。明示的に収集:Calling GC.Collect explicitly:

  • 運用 ASP.NET Core アプリでは 実行しないでください。Should not be done by production ASP.NET Core apps.
  • は、メモリリークを調査するときに便利です。Is useful when investigating memory leaks.
  • 調査時に、GC によってすべての未解決のオブジェクトがメモリから削除されたことを確認し、メモリを測定します。When investigating, verifies the GC has removed all dangling objects from memory so memory can be measured.

アプリのメモリ使用量の分析Analyzing the memory usage of an app

専用ツールは、メモリ使用量の分析に役立ちます。Dedicated tools can help analyzing memory usage:

  • カウント (オブジェクト参照を)Counting object references
  • GC が CPU 使用率に与える影響を測定するMeasuring how much impact the GC has on CPU usage
  • 各世代に使用されるメモリ領域の測定Measuring memory space used for each generation

メモリ使用量を分析するには、次のツールを使用します。Use the following tools to analyze memory usage:

メモリの問題の検出Detecting memory issues

タスクマネージャーを使用して、ASP.NET アプリが使用しているメモリの量を把握できます。Task Manager can be used to get an idea of how much memory an ASP.NET app is using. タスクマネージャーのメモリ値:The Task Manager memory value:

  • ASP.NET プロセスによって使用されるメモリの量を表します。Represents the amount of memory that is used by the ASP.NET process.
  • アプリの生きたオブジェクトや、ネイティブメモリ使用量などの他のメモリコンシューマーを含みます。Includes the app's living objects and other memory consumers such as native memory usage.

タスクマネージャーのメモリ値が無制限に増加し、フラット化されない場合、アプリにはメモリリークが発生します。If the Task Manager memory value increases indefinitely and never flattens out, the app has a memory leak. 次のセクションでは、いくつかのメモリ使用パターンについて説明し、説明します。The following sections demonstrate and explain several memory usage patterns.

ディスプレイメモリ使用量アプリのサンプルSample display memory usage app

Memoryleak サンプルアプリは GitHub で入手できます。The MemoryLeak sample app is available on GitHub. MemoryLeak アプリ:The MemoryLeak app:

  • には、アプリのリアルタイムメモリおよび GC データを収集する診断コントローラーが含まれています。Includes a diagnostic controller that gathers real-time memory and GC data for the app.
  • には、メモリおよび GC データを表示するインデックスページがあります。Has an Index page that displays the memory and GC data. インデックスページは、1秒ごとに更新されます。The Index page is refreshed every second.
  • には、さまざまなメモリ読み込みパターンを提供する API コントローラーが含まれています。Contains an API controller that provides various memory load patterns.
  • はサポートされているツールではありませんが、ASP.NET Core アプリのメモリ使用量パターンを表示するために使用できます。Is not a supported tool, however, it can be used to display memory usage patterns of ASP.NET Core apps.

MemoryLeak を実行します。Run MemoryLeak. 割り当てられたメモリは、GC が発生するまで徐々に増加します。Allocated memory slowly increases until a GC occurs. データをキャプチャするためのカスタムオブジェクトがツールによって割り当てられるため、メモリが増加します。Memory increases because the tool allocates custom object to capture data. 次の図は、Gen 0 GC が発生したときの MemoryLeak インデックスページを示しています。The following image shows the MemoryLeak Index page when a Gen 0 GC occurs. API コントローラーからの API エンドポイントが呼び出されていないため、グラフには0個の RPS (1 秒あたりの要求数) が表示されます。The chart shows 0 RPS (Requests per second) because no API endpoints from the API controller have been called.

前のグラフ

グラフには、メモリ使用量の2つの値が表示されます。The chart displays two values for the memory usage:

  • 割り当て済み: マネージオブジェクトによって占有されているメモリの量Allocated: the amount of memory occupied by managed objects
  • Working set: 現在物理メモリに常駐しているプロセスの仮想アドレス空間にあるページのセット。Working set: The set of pages in the virtual address space of the process that are currently resident in physical memory. 表示される作業セットは、タスクマネージャーに表示される値と同じです。The working set shown is the same value Task Manager displays.

一時オブジェクトTransient objects

次の API は、10 KB の文字列インスタンスを作成し、それをクライアントに返します。The following API creates a 10-KB String instance and returns it to the client. 各要求では、新しいオブジェクトがメモリに割り当てられ、応答に書き込まれます。On each request, a new object is allocated in memory and written to the response. 文字列は .NET で UTF-16 文字として格納されるため、各文字はメモリ内で2バイトを取ります。Strings are stored as UTF-16 characters in .NET so each character takes 2 bytes in memory.

[HttpGet("bigstring")]
public ActionResult<string> GetBigString()
{
    return new String('x', 10 * 1024);
}

次のグラフは、の負荷が比較的小さい場合に生成され、メモリ割り当てが GC によってどのように影響されているかを示します。The following graph is generated with a relatively small load in to show how memory allocations are impacted by the GC.

前のグラフ

前のグラフは次を示しています。The preceding chart shows:

  • 4K RPS (1 秒あたりの要求数)。4K RPS (Requests per second).
  • ジェネレーション0の GC コレクションは、約2秒ごとに発生します。Generation 0 GC collections occur about every two seconds.
  • ワーキングセットは約 500 MB に固定されています。The working set is constant at approximately 500 MB.
  • CPU は12% です。CPU is 12%.
  • メモリ使用量と解放 (GC 経由) が安定しています。The memory consumption and release (through GC) is stable.

次のグラフは、マシンで処理できる最大スループットで取得されます。The following chart is taken at the max throughput that can be handled by the machine.

前のグラフ

前のグラフは次を示しています。The preceding chart shows:

  • 22K RPS22K RPS
  • ジェネレーション0の GC コレクションは1秒間に数回発生します。Generation 0 GC collections occur several times per second.
  • ジェネレーション1のコレクションがトリガーされるのは、アプリによって1秒あたりにかなり多くのメモリが割り当てられたためです。Generation 1 collections are triggered because the app allocated significantly more memory per second.
  • ワーキングセットは約 500 MB に固定されています。The working set is constant at approximately 500 MB.
  • CPU は33% です。CPU is 33%.
  • メモリ使用量と解放 (GC 経由) が安定しています。The memory consumption and release (through GC) is stable.
  • CPU (33%)は過剰に使用されていないため、ガベージコレクションは多くの割り当てを保持できます。The CPU (33%) is not over-utilized, therefore the garbage collection can keep up with a high number of allocations.

ワークステーション GC とサーバー GCWorkstation GC vs. Server GC

.NET ガベージコレクターには、次の2つの異なるモードがあります。The .NET Garbage Collector has two different modes:

  • WORKSTATION GC : デスクトップ用に最適化されています。Workstation GC : Optimized for the desktop.
  • サーバー GCServer GC . ASP.NET Core アプリの既定の GC。The default GC for ASP.NET Core apps. サーバーに合わせて最適化されます。Optimized for the server.

GC モードは、プロジェクトファイルまたは発行されたアプリの runtimeconfig.js ファイルで明示的に設定できます。The GC mode can be set explicitly in the project file or in the runtimeconfig.json file of the published app. 次のマークアップは、プロジェクトファイルの設定を示してい ServerGarbageCollection ます。The following markup shows setting ServerGarbageCollection in the project file:

<PropertyGroup>
  <ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>

プロジェクトファイルでを変更するに ServerGarbageCollection は、アプリを再構築する必要があります。Changing ServerGarbageCollection in the project file requires the app to be rebuilt.

注: サーバーのガベージコレクションは、コアが1つのマシンでは使用でき ませんNote: Server garbage collection is not available on machines with a single core. 詳細については、「IsServerGC」を参照してください。For more information, see IsServerGC.

次の図は、ワークステーション GC を使用した 5K RPS のメモリプロファイルを示しています。The following image shows the memory profile under a 5K RPS using the Workstation GC.

前のグラフ

このグラフとサーバーのバージョンの違いは、次のとおりです。The differences between this chart and the server version are significant:

  • ワーキングセットは 500 MB から 70 MB になります。The working set drops from 500 MB to 70 MB.
  • GC は、2秒ごとではなく、1秒あたり複数回ジェネレーション0のコレクションを行います。The GC does generation 0 collections multiple times per second instead of every two seconds.
  • GC は 300 MB から 10 MB に減少します。GC drops from 300 MB to 10 MB.

一般的な web サーバー環境では、CPU 使用率はメモリよりも重要であるため、サーバー GC の方が適しています。On a typical web server environment, CPU usage is more important than memory, therefore the Server GC is better. メモリ使用率が高く、CPU 使用率が比較的低い場合、ワークステーションの GC のパフォーマンスが向上する可能性があります。If memory utilization is high and CPU usage is relatively low, the Workstation GC might be more performant. たとえば、メモリが不足している複数の web アプリをホストしている高密度です。For example, high density hosting several web apps where memory is scarce.

ドッカーと小さなコンテナを使用した GCGC using Docker and small containers

1台のコンピューターで複数のコンテナー化されたアプリが実行されている場合、ワークステーションの GC はサーバー GC よりも preformant になる可能性があります。When multiple containerized apps are running on one machine, Workstation GC might be more preformant than Server GC. 詳細については、「 小さなコンテナーでのサーバー gc の実行 」と「 小さなコンテナーシナリオでサーバー gc を使用して実行する (パート 1-Gc ヒープのハードリミット)」を参照してください。For more information, see Running with Server GC in a Small Container and Running with Server GC in a Small Container Scenario Part 1 – Hard Limit for the GC Heap.

永続的なオブジェクト参照Persistent object references

GC は参照されているオブジェクトを解放できません。The GC cannot free objects that are referenced. 参照されていて、不要になったオブジェクトは、メモリリークを発生させます。Objects that are referenced but no longer needed result in a memory leak. アプリがオブジェクトを頻繁に割り当てて、不要になった後にオブジェクトを解放できない場合は、メモリ使用量が時間の経過と共に増加します。If the app frequently allocates objects and fails to free them after they are no longer needed, memory usage will increase over time.

次の API は、10 KB の文字列インスタンスを作成し、それをクライアントに返します。The following API creates a 10-KB String instance and returns it to the client. 前の例との違いは、このインスタンスが静的メンバーによって参照されていることです。これは、コレクションでは使用できないことを意味します。The difference with the previous example is that this instance is referenced by a static member, which means it's never available for collection.

private static ConcurrentBag<string> _staticStrings = new ConcurrentBag<string>();

[HttpGet("staticstring")]
public ActionResult<string> GetStaticString()
{
    var bigString = new String('x', 10 * 1024);
    _staticStrings.Add(bigString);
    return bigString;
}

上記のコードでは次の操作が行われます。The preceding code:

  • は、一般的なメモリリークの例です。Is an example of a typical memory leak.
  • 頻繁な呼び出しでは、例外によりプロセスがクラッシュするまで、アプリのメモリが増加し OutOfMemory ます。With frequent calls, causes app memory to increases until the process crashes with an OutOfMemory exception.

前のグラフ

前の図では、次のようになります。In the preceding image:

  • エンドポイントをロードテストする /api/staticstring と、メモリが直線的に増加します。Load testing the /api/staticstring endpoint causes a linear increase in memory.
  • GC は、ジェネレーション2のコレクションを呼び出すことによって、メモリの負荷が増加したときにメモリの解放を試みます。The GC tries to free memory as the memory pressure grows, by calling a generation 2 collection.
  • GC では、リークしたメモリを解放できません。The GC cannot free the leaked memory. 割り当てられたワーキングセットは時間と共に増加します。Allocated and working set increase with time.

キャッシュなどの一部のシナリオでは、メモリ不足によってオブジェクト参照が強制的に解放されるまで、オブジェクト参照を保持する必要があります。Some scenarios, such as caching, require object references to be held until memory pressure forces them to be released. クラスは、 WeakReference この種のキャッシュコードに使用できます。The WeakReference class can be used for this type of caching code. WeakReferenceオブジェクトはメモリ負荷の下で収集されます。A WeakReference object is collected under memory pressures. の既定の実装では、が IMemoryCache 使用さ WeakReference れます。The default implementation of IMemoryCache uses WeakReference.

ネイティブメモリNative memory

.NET Core オブジェクトの中には、ネイティブメモリに依存しているものがあります。Some .NET Core objects rely on native memory. GC でネイティブメモリを収集することはでき ませんNative memory can not be collected by the GC. ネイティブメモリを使用している .NET オブジェクトは、ネイティブコードを使用して解放する必要があります。The .NET object using native memory must free it using native code.

.NET には、 IDisposable 開発者がネイティブメモリを解放できるようにするインターフェイスが用意されています。.NET provides the IDisposable interface to let developers release native memory. Disposeが呼び出されていない場合でも、 Dispose ファイナライザーの実行時に、正しく実装されたクラスが呼び出されます。Even if Dispose is not called, correctly implemented classes call Dispose when the finalizer runs.

次のコードがあるとします。Consider the following code:

[HttpGet("fileprovider")]
public void GetFileProvider()
{
    var fp = new PhysicalFileProvider(TempPath);
    fp.Watch("*.*");
}

Physicalfileprovider はマネージクラスであるため、すべてのインスタンスが要求の最後に収集されます。PhysicalFileProvider is a managed class, so any instance will be collected at the end of the request.

次の図は、API を継続的に呼び出すときのメモリプロファイルを示して fileprovider います。The following image shows the memory profile while invoking the fileprovider API continuously.

前のグラフ

前のグラフは、このクラスの実装に関する明らかな問題を示しています。これは、メモリ使用量が増加し続けるためです。The preceding chart shows an obvious issue with the implementation of this class, as it keeps increasing memory usage. これは、 この問題で追跡されている既知の問題です。This is a known problem that is being tracked in this issue.

次のいずれかの方法で、ユーザーコードで同じリークが発生する可能性があります。The same leak could be happen in user code, by one of the following:

  • クラスを正しく解放できません。Not releasing the class correctly.
  • Dispose破棄する依存オブジェクトのメソッドを呼び出そうとしていません。Forgetting to invoke the Disposemethod of the dependent objects that should be disposed.

ラージオブジェクトヒープLarge objects heap

メモリの割り当てや空きサイクルが頻繁に発生する場合は、特にメモリの大量のチャンクを割り当てるときにメモリをフラグメント化できます。Frequent memory allocation/free cycles can fragment memory, especially when allocating large chunks of memory. オブジェクトは、連続したメモリブロックで割り当てられます。Objects are allocated in contiguous blocks of memory. 断片化を軽減するために、GC はメモリを解放するときに、メモリを最適化しようとします。To mitigate fragmentation, when the GC frees memory, it tries to defragment it. このプロセスは、 圧縮 と呼ばれます。This process is called compaction . 圧縮には、オブジェクトの移動が含まれます。Compaction involves moving objects. 大きなオブジェクトを移動すると、パフォーマンスが低下します。Moving large objects imposes a performance penalty. このため、GC は大きなオブジェクト ヒープ(LOH) と呼ばれる 大きな オブジェクト用に特別なメモリゾーンを作成します。For this reason the GC creates a special memory zone for large objects, called the large object heap (LOH). 85000バイトを超えるオブジェクト (約 83 KB) は次のとおりです。Objects that are greater than 85,000 bytes (approximately 83 KB) are:

  • LOH に配置されます。Placed on the LOH.
  • 圧縮されていません。Not compacted.
  • ジェネレーション2の Gc 中に収集されます。Collected during generation 2 GCs.

LOH がいっぱいになると、GC はジェネレーション2のコレクションをトリガーします。When the LOH is full, the GC will trigger a generation 2 collection. ジェネレーション2のコレクション:Generation 2 collections:

  • は本質的に低速です。Are inherently slow.
  • さらに、他のすべての世代でコレクションをトリガーするコストも発生します。Additionally incur the cost of triggering a collection on all other generations.

次のコードは、LOH を直ちに最適化します。The following code compacts the LOH immediately:

GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();

LOH の圧縮の詳細については、「」を参照してください LargeObjectHeapCompactionModeSee LargeObjectHeapCompactionMode for information on compacting the LOH.

.NET Core 3.0 以降を使用するコンテナーでは、LOH が自動的に圧縮されます。In containers using .NET Core 3.0 and later, the LOH is automatically compacted.

次の API は、この動作を示しています。The following API that illustrates this behavior:

[HttpGet("loh/{size=85000}")]
public int GetLOH1(int size)
{
   return new byte[size].Length;
}

次のグラフは、エンドポイントを呼び出したときの、最大負荷下でのメモリプロファイルを示してい /api/loh/84975 ます。The following chart shows the memory profile of calling the /api/loh/84975 endpoint, under maximum load:

前のグラフ

次のグラフは、エンドポイントの呼び出しのメモリプロファイルを示してい /api/loh/84976 ます。 1 バイトだけ 割り当てられます。The following chart shows the memory profile of calling the /api/loh/84976 endpoint, allocating just one more byte :

前のグラフ

注: byte[] 構造体にはオーバーヘッドバイトがあります。Note: The byte[] structure has overhead bytes. そのため、84976バイトは85000の制限をトリガーします。That's why 84,976 bytes triggers the 85,000 limit.

前の2つのグラフを比較します。Comparing the two preceding charts:

  • ワーキングセットは、2つのシナリオ (約 450 MB) で似ています。The working set is similar for both scenarios, about 450 MB.
  • [LOH 要求 (84975 バイト)] の下には、ほとんどがジェネレーション0のコレクションが表示されます。The under LOH requests (84,975 bytes) shows mostly generation 0 collections.
  • LOH を超える要求では、定数ジェネレーション2のコレクションが生成されます。The over LOH requests generate constant generation 2 collections. ジェネレーション2のコレクションはコストが高くなります。Generation 2 collections are expensive. より多くの CPU が必要であり、スループットは約50% 低下します。More CPU is required and throughput drops almost 50%.

一時的なラージオブジェクトは、gen2 Gc を引き起こすため、特に問題になります。Temporary large objects are particularly problematic because they cause gen2 GCs.

最大のパフォーマンスを向上させるには、大きなオブジェクトの使用を最小限にする必要があります。For maximum performance, large object use should be minimized. 可能であれば、大きなオブジェクトを分割します。If possible, split up large objects. たとえば、ASP.NET Core の 応答キャッシュ ミドルウェアでは、キャッシュエントリが85000バイト未満のブロックに分割されます。For example, Response Caching middleware in ASP.NET Core split the cache entries into blocks less than 85,000 bytes.

次のリンクでは、オブジェクトを LOH の制限下に維持するための ASP.NET Core アプローチについて説明します。The following links show the ASP.NET Core approach to keeping objects under the LOH limit:

詳細については、次をご覧ください。For more information, see:

HttpClientHttpClient

を誤って使用する HttpClient と、リソースリークが発生する可能性があります。Incorrectly using HttpClient can result in a resource leak. データベース接続、ソケット、ファイルハンドルなどのシステムリソース:System resources, such as database connections, sockets, file handles, etc.:

  • はメモリよりも不足しています。Are more scarce than memory.
  • メモリよりもリークが発生した場合、より問題が発生します。Are more problematic when leaked than memory.

経験豊富な .NET 開発者は、を Dispose 実装するオブジェクトに対してを呼び出すことがわかって IDisposable います。Experienced .NET developers know to call Dispose on objects that implement IDisposable. を実装するオブジェクトを破棄 IDisposable しないと、メモリリークやシステムリソースのリークが発生します。Not disposing objects that implement IDisposable typically results in leaked memory or leaked system resources.

HttpClientIDisposable を実装しますが、すべての呼び出しで破棄することはでき ませんHttpClient implements IDisposable, but should not be disposed on every invocation. 代わりに、を HttpClient 再利用する必要があります。Rather, HttpClient should be reused.

次のエンドポイントは、 HttpClient すべての要求に対して新しいインスタンスを作成し、破棄します。The following endpoint creates and disposes a new HttpClient instance on every request:

[HttpGet("httpclient1")]
public async Task<int> GetHttpClient1(string url)
{
    using (var httpClient = new HttpClient())
    {
        var result = await httpClient.GetAsync(url);
        return (int)result.StatusCode;
    }
}

負荷の下で、次のエラーメッセージがログに記録されます。Under load, the following error messages are logged:

fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HLG70PBE1CR1", Request id "0HLG70PBE1CR1:00000031":
      An unhandled exception was thrown by the application.
System.Net.Http.HttpRequestException: Only one usage of each socket address
    (protocol/network address/port) is normally permitted --->
    System.Net.Sockets.SocketException: Only one usage of each socket address
    (protocol/network address/port) is normally permitted
   at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port,
    CancellationToken cancellationToken)

HttpClientインスタンスが破棄されている場合でも、オペレーティングシステムによって実際のネットワーク接続が解放されるまでに時間がかかります。Even though the HttpClient instances are disposed, the actual network connection takes some time to be released by the operating system. 新しい接続を継続的に作成することで、 ポートの枯渇 が発生します。By continuously creating new connections, ports exhaustion occurs. 各クライアント接続には、独自のクライアントポートが必要です。Each client connection requires its own client port.

ポートの枯渇を防ぐ方法の1つは、同じインスタンスを再利用することです HttpClientOne way to prevent port exhaustion is to reuse the same HttpClient instance:

private static readonly HttpClient _httpClient = new HttpClient();

[HttpGet("httpclient2")]
public async Task<int> GetHttpClient2(string url)
{
    var result = await _httpClient.GetAsync(url);
    return (int)result.StatusCode;
}

HttpClientインスタンスは、アプリが停止したときに解放されます。The HttpClient instance is released when the app stops. この例では、すべての破棄可能なリソースを使用後に破棄する必要がないことを示しています。This example shows that not every disposable resource should be disposed after each use.

インスタンスの有効期間をより適切に処理する方法については、次を参照してください HttpClientSee the following for a better way to handle the lifetime of an HttpClient instance:

オブジェクト プーリングObject pooling

前の例では、 HttpClient インスタンスを静的にし、すべての要求で再利用する方法を示しました。The previous example showed how the HttpClient instance can be made static and reused by all requests. 再利用すると、リソースが不足するのを防ぐことができます。Reuse prevents running out of resources.

オブジェクトプール:Object pooling:

  • 再利用パターンを使用します。Uses the reuse pattern.
  • は、作成に負荷がかかるオブジェクト向けに設計されています。Is designed for objects that are expensive to create.

プールは、スレッド間で予約および解放できる事前に初期化されたオブジェクトのコレクションです。A pool is a collection of pre-initialized objects that can be reserved and released across threads. プールでは、制限、事前定義されたサイズ、増加率などの割り当てルールを定義できます。Pools can define allocation rules such as limits, predefined sizes, or growth rate.

NuGet パッケージの Microsoft extension. ObjectPool には、このようなプールの管理に役立つクラスが含まれています。The NuGet package Microsoft.Extensions.ObjectPool contains classes that help to manage such pools.

次の API エンドポイントは、 byte 各要求に対してランダムな数値を格納するバッファーをインスタンス化します。The following API endpoint instantiates a byte buffer that is filled with random numbers on each request:

        [HttpGet("array/{size}")]
        public byte[] GetArray(int size)
        {
            var random = new Random();
            var array = new byte[size];
            random.NextBytes(array);

            return array;
        }

次のグラフは、中程度の負荷で前の API を呼び出したときに表示されます。The following chart display calling the preceding API with moderate load:

前のグラフ

前のグラフでは、ジェネレーション0のコレクションは1秒間に約1回発生します。In the preceding chart, generation 0 collections happen approximately once per second.

上記のコードは、 byte arraypool <T> を使用してバッファーをプールすることによって最適化できます。The preceding code can be optimized by pooling the byte buffer by using ArrayPool<T>. 静的インスタンスは、要求間で再利用されます。A static instance is reused across requests.

この方法の違いは、プールされたオブジェクトが API から返されることです。What's different with this approach is that a pooled object is returned from the API. これは次のことを意味します。That means:

  • オブジェクトは、メソッドから戻るとすぐにコントロールから除外されます。The object is out of your control as soon as you return from the method.
  • オブジェクトを解放することはできません。You can't release the object.

オブジェクトの破棄を設定するには、次のようにします。To set up disposal of the object:

  • プールされた配列を破棄可能なオブジェクトにカプセル化します。Encapsulate the pooled array in a disposable object.
  • プールされたオブジェクトを httpcontext.currentに登録します。Register the pooled object with HttpContext.Response.RegisterForDispose.

RegisterForDispose は、 Dispose HTTP 要求が完了したときにのみ解放されるように、ターゲットオブジェクトでを呼び出すことを処理します。RegisterForDispose will take care of calling Disposeon the target object so that it's only released when the HTTP request is complete.

private static ArrayPool<byte> _arrayPool = ArrayPool<byte>.Create();

private class PooledArray : IDisposable
{
    public byte[] Array { get; private set; }

    public PooledArray(int size)
    {
        Array = _arrayPool.Rent(size);
    }

    public void Dispose()
    {
        _arrayPool.Return(Array);
    }
}

[HttpGet("pooledarray/{size}")]
public byte[] GetPooledArray(int size)
{
    var pooledArray = new PooledArray(size);

    var random = new Random();
    random.NextBytes(pooledArray.Array);

    HttpContext.Response.RegisterForDispose(pooledArray);

    return pooledArray.Array;
}

プールされていないバージョンと同じ負荷を適用すると、次のグラフが表示されます。Applying the same load as the non-pooled version results in the following chart:

前のグラフ

主な違いは、バイトが割り当てられ、その結果、ジェネレーション0のコレクションがはるかに少ないことです。The main difference is allocated bytes, and as a consequence much fewer generation 0 collections.

その他の資料Additional resources