.NET Framework メモリ管理

この記事では、.NET Frameworkでメモリを操作する際のいくつかのポイントについて説明します。 この記事では、GCの動作、GCがそのヒップ、GC動作モードを制御する方法について説明します。 GCをバイパスするメモリ使用量の例を示します。 簡単にアクセスできる情報だけでなく、.NETで記述されたアプリケーションのダンプを調べるときにのみ利用できる情報も提示しました。 この記事が有益であり、あまり退屈ではないことを願っています。 次の記事では、ローダー、JIT、およびメソッドテーブル、メソッド記述子、EEClassなどのデータ構造について説明します。



理論



管理されていないメモリ



仮想メモリは、プロセスの物理メモリに必ずしも反映されないメモリの論理表現です。 32ビットオペレーティングシステムでは、4Gbの仮想アドレススペースがプロセスに割り当てられます。 デフォルトは、ユーザーモードごとに2Gbです。 カーネルモードではなく、ユーザーモードに焦点を当てます。

プロセスがメモリを割り当てる必要がある場合、最初にそれを予約し、次にメモリをコミットする必要があります。 このバックアップ/コミットプロセスは、APIを使用して仮想メモリを操作する方法に応じて、1つまたは2つのステップで実行できます。

通常、予約メモリは次の部分に分割されます。



マネージメモリは部分的に割り当てられます。 メモリの割り当ては、16、32、および64 KBです。 それらは、割り込み不可能なブロックに割り当てられる必要があり、割り当てる十分なメモリ領域がない場合、プロセスはOutOfMemoryExceptionをスローします。 プロセスがガベージコレクションを完了できない場合、ガベージコレクタの内部構造に十分なメモリがないため、プロセスはクラッシュします。

この例外の後、プロセスは不安定な状態になる可能性があるため、アプリケーションを調べて、致命的ではないOutOfMemoryException例外も回避する必要があります。

ユーザーモードのメモリマネージャーの役​​割は、仮想メモリの冗長性を管理することです。 メモリマネージャは、小さなフラグメンテーション、大きなブロックの冗長性など、メモリを管理するときにさまざまな目標を達成するためにいくつかのアルゴリズムを使用します。

プロセスはメモリを使用する前に、メモリの少なくとも一部を予約してコミットする必要があります。 これら2つのステップは、 VirtualAllocVirtualAllocEx API関数を使用して実行できます。 VirtualFreeまたはVirtualFreeExを使用してメモリを解放できます 。 最後の関数を呼び出すと、メモリブロックの状態が「固定」から「空き」に変わります。

C ++開発者からアイデアを盗みます。 メモリを集中的に使用するために、GCを使用できない場合があります(後で説明します)。 このような状況はまれであり、特定の制限の下で発生します。 この場合、mallocおよびfreeを実装できます。 Malloc - HeapAllocを呼び出し、free- HeapFree呼び出します。 HeapCreateを呼び出すことで、独自のヒップを作成できます。 Windows環境でメモリを操作するための機能の完全なリストは、「 メモリ管理機能」にあります。

私はLinux開発者ではないので、これらのAPI関数の呼び出しを置き換える方法を言うことはできません。 これらの関数も使用する必要がある人のために、アプリケーションを近い将来Monoプラットフォームに移植する予定がない場合でも、 Abstract factory patternを使用してこれを実装することを提案します。 これらの関数を使用することは、移植性にいくつかの困難をもたらすため、一般的にはあまり正しくありませんが、いくつかの非常に特定の状況では、GCの圧力を減らすためにこれを使用する必要があります。



管理メモリ



プロセスメモリの構成は次のとおりです。



さて、ここでGCヒップに来ます。 ガベージコレクター(GC)は、マネージコードのメモリを割り当てて解放します。 GCはVirtualAllocを使用して、ヒープ用にメモリを予約します。 ヒープのサイズは、GCモードと.NET Frameworkのバージョンによって異なります。 サイズは16、32、または64MBです。 Hip GCは、他のプロセスヒープから分離され、.NETランタイムによって管理される仮想メモリの切っても切れないブロックです。 興味深いことに、GCはメモリ全体をすぐにキャプチャするのではなく、メモリが大きくなったときにのみキャプチャします。 GCは、管理されたヒープの最後で次の空きアドレスを追跡し、必要に応じて、次のメモリブロックを要求します。 ラージオブジェクト用に個別のヒップが作成されます(.NET Framework 2.0、1.1では、ラージオブジェクトは世代が同じヒップにありますが、異なるセグメントにあります)。 ラージオブジェクト-85,000バイトを超えるオブジェクト。

GCの作業に焦点を当てます。 GCは3世代(0、1、2)とLOH(大きなオブジェクトのヒップ)を使用します。 作成されたオブジェクトはゼロ世代に分類されます。 ゼロ世代のサイズがしきい値に達すると(セグメントにそれ以上のメモリがない)、新しいオブジェクトの作成が不可能になると、ゼロ世代のガベージコレクションが開始されます。 第1世代のセグメントでメモリが不足している場合、ガベージコレクションは第1世代でゼロになります。 第2世代のガベージコレクションを行う場合、第1世代とゼロ世代のガベージコレクションもあります。

ゼロ世代のガベージコレクション後に生き残ったオブジェクトは、最初から2番目のオブジェクトに移動します。 上記に基づいて、ガベージコレクションを手動で呼び出すことは強くお勧めしません。これは、アプリケーションのパフォーマンスに大きく影響する可能性があるためです(ガベージコレクションの呼び出しが正しい場合の例を探して、この問題を議論するコメントを書いてください)。

大きなオブジェクトの場合、世代はありません。 1.1以降の新しい.NET Frameworkでは、ヒープの最後にあるオブジェクトが削除されると、プロセスのプライベートバイトが削減されます。

GCの動作に関する非常に興味深い質問-ガベージコレクションを実行すると、実際にはどうなりますか GCは、世代0、1、2、または完全なガベージコレクションに対して、ガベージコレクションが発生するかどうかに関係なく、いくつかの手順を実行します。 ガベージコレクションの手順:



GCの動作モード:



GCの目的の動作モードを有効にする方法。 構成ファイルの構成/ランタイムセクション:



GCのパフォーマンスを向上させると、次の問題を解決できます。



GCは参照を使用して、オブジェクトが占有しているメモリを解放できるかどうかを判断します。 ガベージコレクションの前に、GCはその祖先から開始し、それらをツリーで構築するリンクを上に移動します。 すべてのオブジェクトへのリンクのリストを使用して、アクセスできず、ガベージコレクションの準備ができているオブジェクトを判別します。

リンクにはいくつかのタイプがあります。



ファイナライズについて書きたかったのですが、その情報はいっぱいです。 注目すべき唯一のことは、各プロセスに追加のファイナライズスレッドが追加され、このスレッドをロックでブロックするとプロセスが中断される可能性があることです。



GCなしでメモリを操作するいくつかの例



using System; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Security; namespace TestApplication { #region NativeMethods internal static class NativeMethods { #region Virtual memory #region VirtualAlloc [Flags()] public enum AllocationType : uint { MEM_COMMIT = 0x1000, MEM_RESERVE = 0x2000, MEM_RESET = 0x80000, [Obsolete("Windows XP/2000: This flag is not supported.")] MEM_LARGE_PAGES = 0x20000000, MEM_PHYSICAL = 0x400000, MEM_TOP_DOWN = 0x100000, [Obsolete("Windows 2000: This flag is not supported.")] MEM_WRITE_WATCH = 0x200000, } [Flags()] public enum MemoryProtection : uint { PAGE_EXECUTE = 0x10, PAGE_EXECUTE_READ = 0x20, PAGE_EXECUTE_READWRITE = 0x40, [Obsolete("This flag is not supported by the VirtualAlloc or VirtualAllocEx functions. It is not supported by the CreateFileMapping function until Windows Vista with P1 andWindows Server 2008.")] PAGE_EXECUTE_WRITECOPY = 080, PAGE_NOACCESS = 0x01, PAGE_READONLY = 0x02, PAGE_READWRITE = 0x04, [Obsolete("This flag is not supported by the VirtualAlloc or VirtualAllocEx functions.")] PAGE_WRITECOPY = 0x08, PAGE_GUARD = 0x100, PAGE_NOCACHE = 0x200, PAGE_WRITECOMBINE = 0x400, } [DllImport( "kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi)] internal static extern IntPtr VirtualAlloc( IntPtr lpAddress, IntPtr dwSize, AllocationType flAllocationType, MemoryProtection flProtect); #endregion #region VirtualFree [Flags()] public enum FreeType : uint { MEM_DECOMMIT = 0x4000, MEM_RELEASE = 0x8000, } [DllImport( "kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool VirtualFree( IntPtr lpAddress, IntPtr dwSize, FreeType dwFreeType); #endregion #endregion #region Heap #region HeapCreate [Flags()] public enum HeapOptions : uint { Empty = 0x00000000, HEAP_CREATE_ENABLE_EXECUTE = 0x00040000, HEAP_GENERATE_EXCEPTIONS = 0x00000004, HEAP_NO_SERIALIZE = 0x00000001, } [DllImport( "kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi)] internal static extern IntPtr HeapCreate( HeapOptions flOptions, IntPtr dwInitialSize, IntPtr dwMaximumSize); #endregion #region HeapDestroy [DllImport( "kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool HeapDestroy( IntPtr hHeap); #endregion #region HeapAlloc [Flags()] public enum HeapAllocFlags : uint { Empty = 0x00000000, HEAP_GENERATE_EXCEPTIONS = 0x00000004, HEAP_NO_SERIALIZE = 0x00000001, HEAP_ZERO_MEMORY = 0x00000008, } [DllImport( "kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi)] internal static extern unsafe void* HeapAlloc( HeapHandle hHeap, HeapAllocFlags dwFlags, IntPtr dwBytes); #endregion #region HeapFree [Flags()] public enum HeapFreeFlags : uint { Empty = 0x00000000, HEAP_NO_SERIALIZE = 0x00000001, } [DllImport( "kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern unsafe bool HeapFree( HeapHandle hHeap, HeapFreeFlags dwFlags, void* lpMem); #endregion #endregion } #endregion #region Memory handles #region VirtualMemoryHandle internal sealed class VirtualMemoryHandle : SafeHandle { public VirtualMemoryHandle(IntPtr handle, IntPtr size) : base(handle, true) { Size = size; } [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] override protected bool ReleaseHandle() { return NativeMethods.VirtualFree( handle, Size, NativeMethods.FreeType.MEM_RELEASE); } public unsafe void* GetPointer(out IntPtr sizeOfChunk) { return GetPointer(IntPtr.Zero, out sizeOfChunk); } public unsafe void* GetPointer(IntPtr offset, out IntPtr sizeOfChunk) { if (IsInvalid || (offset.ToInt64() > Size.ToInt64())) { sizeOfChunk = IntPtr.Zero; return (void*)IntPtr.Zero; } sizeOfChunk = (IntPtr)(Size.ToInt64() - offset.ToInt64()); return (byte*)handle + offset.ToInt64(); } public unsafe void* GetPointer() { return GetPointer(IntPtr.Zero); } public unsafe void* GetPointer(IntPtr offset) { if (IsInvalid || (offset.ToInt64() > Size.ToInt64())) { return (void*)IntPtr.Zero; } return (byte*)handle + offset.ToInt64(); } public override bool IsInvalid { get { return handle == IntPtr.Zero; } } public IntPtr Size { get; private set; } } #endregion #region HeapHandle internal sealed class HeapHandle : SafeHandle { public HeapHandle(IntPtr handle) : base(handle, true) { } [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] override protected bool ReleaseHandle() { return NativeMethods.HeapDestroy(handle); } public unsafe void* Malloc(IntPtr size) { if (IsInvalid) { return (void*)IntPtr.Zero; } return NativeMethods.HeapAlloc( this, NativeMethods.HeapAllocFlags.Empty, size); } public unsafe bool Free(void* lpMem) { if (lpMem == null) { return false; } return NativeMethods.HeapFree( this, NativeMethods.HeapFreeFlags.Empty, lpMem); } public override bool IsInvalid { get { return handle == IntPtr.Zero; } } } #endregion #endregion class Program { static void Main() { IntPtr memoryChunkSize = (IntPtr)(1024 * 1024); IntPtr stackAllocation = (IntPtr)(1024); #region Example 1 Console.WriteLine("Example 1 (VirtualAlloc, VirtualFree):"); IntPtr memoryForSafeHandle = NativeMethods.VirtualAlloc( IntPtr.Zero, memoryChunkSize, NativeMethods.AllocationType.MEM_RESERVE | NativeMethods.AllocationType.MEM_COMMIT, NativeMethods.MemoryProtection.PAGE_EXECUTE_READWRITE); using (VirtualMemoryHandle memoryHandle = new VirtualMemoryHandle(memoryForSafeHandle, memoryChunkSize)) { Console.WriteLine( (!memoryHandle.IsInvalid) ? ("Allocated") : ("Not allocated")); if (!memoryHandle.IsInvalid) { bool memoryCorrect = true; unsafe { int* arrayOfInt = (int*)memoryHandle.GetPointer(); long size = memoryHandle.Size.ToInt64(); for (int index = 0; index < size / sizeof(int); index++) { arrayOfInt[index] = index; } for (int index = 0; index < size / sizeof(int); index++) { if (arrayOfInt[index] != index) { memoryCorrect = false; break; } } } Console.WriteLine( (memoryCorrect) ? ("Write/Read success") : ("Write/Read failed")); } } #endregion #region Example 2 Console.WriteLine("Example 2 (HeapCreate, HeapDestroy, HeapAlloc, HeapFree):"); IntPtr heapForSafeHandle = NativeMethods.HeapCreate( NativeMethods.HeapOptions.Empty, memoryChunkSize, IntPtr.Zero); using (HeapHandle heap = new HeapHandle(heapForSafeHandle)) { Console.WriteLine( (!heap.IsInvalid) ? ("Heap created") : ("Heap is not created")); if (!heap.IsInvalid) { bool memoryCorrect = true; unsafe { int* arrayOfInt = (int*)heap.Malloc(memoryChunkSize); if (arrayOfInt != null) { long size = memoryChunkSize.ToInt64(); for (int index = 0; index < size / sizeof(int); index++) { arrayOfInt[index] = index; } for (int index = 0; index < size / sizeof(int); index++) { if (arrayOfInt[index] != index) { memoryCorrect = false; break; } } if (!heap.Free(arrayOfInt)) { memoryCorrect = false; } } else { memoryCorrect = false; } } Console.WriteLine( (memoryCorrect) ? ("Allocation/Write/Read success") : ("Allocation/Write/Read failed")); } } #endregion #region Example 3 Console.WriteLine("Example 3 (stackalloc):"); unsafe { bool memoryCorrect = true; int* arrayOfInt = stackalloc int[(int)stackAllocation.ToInt64()]; long size = stackAllocation.ToInt64(); for (int index = 0; index < size / sizeof(int); index++) { arrayOfInt[index] = index; } for (int index = 0; index < size / sizeof(int); index++) { if (arrayOfInt[index] != index) { memoryCorrect = false; break; } } Console.WriteLine( (memoryCorrect) ? ("Allocation/Write/Read success") : ("Allocation/Write/Read failed")); } #endregion #region Example 4 Console.WriteLine("Example 4 (Marshal.AllocHGlobal):"); unsafe { bool memoryCorrect = true; var globalPointer = Marshal.AllocHGlobal(memoryChunkSize); int* arrayOfInt = (int*)globalPointer; if (IntPtr.Zero != globalPointer) { try { long size = memoryChunkSize.ToInt64(); for (int index = 0; index < size / sizeof(int); index++) { arrayOfInt[index] = index; } for (int index = 0; index < size / sizeof(int); index++) { if (arrayOfInt[index] != index) { memoryCorrect = false; break; } } } finally { Marshal.FreeHGlobal(globalPointer); } } else { memoryCorrect = false; } Console.WriteLine( (memoryCorrect) ? ("Allocation/Write/Read success") : ("Allocation/Write/Read failed")); } #endregion Console.ReadKey(); } } }
      
      






All Articles