Der große Objektheap auf Windows-Systemen

Der .NET Garbage Collector (GC) teilt Objekte in die Kategorien „klein“ und „groß“ ein. Wenn ein Objekt groß ist, sind einige seiner Attribute wichtiger als bei einem kleinen Objekt. Die Komprimierung, also das Kopieren in den Arbeitsspeicher an einer anderen Stelle des Heaps, kann aufwendig sein. Deshalb platziert der Garbage Collector große Objekte im Large-Object-Heap (LOH). In diesem Artikel wird erläutert, wodurch ein Objekt als großes Objekt angesehen wird, wie große Objekte bereinigt werden und in welcher Weise sich große Objekte auf die Leistung auswirken.

Wichtig

In diesem Artikel wird der Large-Object-Heap in .NET Framework und .NET Core erläutert, der nur in Windows-Systemen ausgeführt werden kann. Der große Objektheap in .NET-Implementierungen auf anderen Plattformen wird nicht behandelt.

Wie gelangt ein Objekt in den Large-Object-Heap?

Ein Objekt wird als großes Objekt betrachtet, wenn es eine Größe von mindestens 85.000 Byte aufweist. Diese Zahl wurde von der Leistungsoptimierung ermittelt. Wenn die Zuordnungsanforderung für ein Objekt über 85.000 Byte beträgt, ordnet die Runtime diese dem großen Objektheap zu.

Der folgende Abschnitt enthält für ein besseres Verständnis einige Grundlagen über den Garbage Collector.

Der Garbage Collector ist ein generationsbasierter Collector. Er verfügt über drei Generationen: Generation 0, Generation 1 und Generation 2. Es gibt drei Generationen, da in einer gut eingerichteten App die meisten Objekte in Generation 0 inaktiv werden. In einer Server-App sollten die Zuordnungen für jede Anforderung inaktiv werden, nachdem die Anforderung abgeschlossen wurde. Die noch nicht abgeschlossenen Zuordnungsanforderungen gelangen in Generation 1 und werden dann inaktiv. Im Grunde dient Generation 1 als Puffer zwischen Bereichen mit neuen Objekten und Bereichen mit langlebigen Objekten.

Neu zugeordnete Objekte bilden eine neue Generation von Objekten und sind implizit Sammlungen der Generation 0. Wenn es sich jedoch um große Objekte handelt, werden sie im großen Objektheap verwaltet, der manchmal auch als Generation 3 bezeichnet wird. Generation 3 ist eine physische Generation, die logisch als Teil der Generation 2 erfasst wird.

Große Objekte gehören zu Generation 2, da sie nur während einer Garbage Collection für Generation 2 bereinigt werden. Wenn eine Generation bereinigt wird, werden alle jüngeren Generationen ebenfalls bereinigt. So findet beispielsweise bei einer Garbage Collection für Generation 1 eine Bereinigung der Generationen 1 und 0 statt. Erfolgt eine Garbage Collection für Generation 2, wird der gesamte Heap bereinigt. Aus diesem Grund wird eine Garbage Collection für Generation 2 auch als vollständige Garbage Collection bezeichnet. In diesem Artikel wird „Garbage Collection für Generation 2“ statt „vollständige Garbage Collection“ verwendet, die Begriffe sind jedoch synonym.

Die Generationen stellen eine logische Ansicht des Garbage Collection-Heaps bereit. Physisch gesehen werden Objekte in verwalteten Heapsegmenten gespeichert. Ein verwaltetes Heapsegment ist ein Speicherblock, den der Garbage Collector vom Betriebssystem (durch Aufrufen der VirtualAlloc-Funktion) für verwalteten Code reserviert. Beim Laden der CLR ordnet der Garbage Collector zunächst zwei Heapsegmente zu: eines für kleine Objekte (der kleine Objektheap) und eines für große Objekte (der große Objektheap).

Die Zuordnungsanforderungen werden dann erfüllt, indem verwaltete Objekte in diesen verwalteten Heapsegmenten abgelegt werden. Wenn das Objekt kleiner als 85.000 Byte ist, wird es im Segment für den kleinen Objektheap abgelegt, andernfalls im Segment für einen großen Objektheap. Segmente werden (in kleineren Blöcken) übernommen, wenn ihnen immer mehr Objekte zugeordnet werden. Beim kleinen Objektheap werden Objekte, die bei einer Garbage Collection nicht bereinigt werden, in die nächste Generation heraufgestuft. Objekte, die in einer Garbage Collection für Generation 0 nicht bereinigt werden, werden nun als Objekte von Generation 1 betrachtet und so weiter. Objekte, die die älteste Generation überdauern, werden jedoch weiterhin als Objekte der ältesten Generation betrachtet. Die Objekte, die Generation 2 überdauern, sind also Objekte von Generation 2, und die Objekte, die den großen Objektheap überdauern, sind Objekte des großen Objektheaps (die in Generation 2 bereinigt werden).

Benutzercode kann nur in der Generation 0 (kleine Objekte) oder im großen Objektheap (große Objekte) zugeordnet werden. Nur der Garbage Collector kann Objekte von Generation 1 (durch Heraufstufen beibehaltener Objekte aus Generation 0) und Generation 2 (durch Heraufstufen beibehaltener Objekte aus den Generationen 1) zuordnen.

Wenn eine Garbage Collection ausgelöst wird, geht der Garbage Collector alle aktiven Objekte durch und komprimiert sie. Da die Komprimierung jedoch aufwändig ist, bereinigt die Garbage Collection den großen Objektheap. Dabei wird eine Freiliste aus inaktiven Objekten erstellt, die später zur Erfüllung von Zuordnungsanforderungen für große Objekte wiederverwendet werden kann. Angrenzende inaktive Objekte werden in ein einzelnes freies Objekt umgewandelt.

.NET Core und .NET Framework (ab .NET Framework 4.5.1) enthalten die GCSettings.LargeObjectHeapCompactionMode-Eigenschaft, durch die Benutzer angegeben können, dass der große Objektheap während der nächsten vollständigen blockierenden Garbage Collection komprimiert werden soll. Zukünftig erfolgt die Komprimierung des großen Objektheaps in .NET möglicherweise automatisch. Daher sollten Sie diese Objekte fixieren, wenn Sie große Objekte zuordnen und sicherstellen möchten, dass diese nicht verschoben werden.

Abbildung 1 zeigt ein Szenario, bei dem Generation 1 nach der ersten Garbage Collection für Generation 0 gebildet wird, bei der Obj1 und Obj3 inaktiv sind. Generation 2 wird nach der ersten Garbage Collection für Generation 1 gebildet, bei der Obj2 und Obj5 inaktiv sind. Beachten Sie, dass diese und die folgenden Abbildungen nur zur Veranschaulichung dienen. Sie enthalten nur wenige Objekte, um besser darzustellen, was auf dem Heap geschieht. In der Praxis sind an einer Garbage Collection in der Regel wesentlich mehr Objekte beteiligt.

Figure 1: A gen 0 GC and a gen 1 GC
Abbildung 1: Garbage Collection für Generation 0 und 1.

Abbildung 2 veranschaulicht, dass nach einer Garbage Collection für Generation 2, bei der Obj1 und Obj2 inaktiv sind, die Garbage Collection freien Arbeitsspeicher erstellt, der von Obj1 und Obj2 belegt werden kann. Diese werden dann verwendet, um eine Zuordnungsanforderung für Obj4 zu erfüllen. Der Speicherplatz nach dem letzten Objekt (Obj3) bis zum Ende des Segments kann ebenfalls zur Erfüllung von Zuordnungsanforderungen verwendet werden.

Figure 2: After a gen 2 GC
Abbildung 2: Nach einer Garbage Collection für Generation 2

Wenn nicht genügend Speicherplatz zur Erfüllung der Zuordnungsanforderungen für große Objekte verfügbar ist, versucht die Garbage Collection zunächst, weitere Segmente vom Betriebssystem anzufordern. Wenn dies fehlschlägt, wird eine Garbage Collection für Generation 2 ausgelöst, um Speicherplatz freizugeben.

Bei einer Garbage Collection für Generation 1 oder 2 gibt der Garbage Collector Segmente frei für das Betriebssystem, in denen keine aktiven Objekte vorhanden sind, indem die Funktion VirtualFree aufgerufen wird. Der Speicherplatz nach dem letzten aktiven Objekt bis zum Ende des Segments wird aufgehoben (außer in dem kurzlebigen Segment, in dem Generation 0 und Generation 1 aktiv sind und in dem der Garbage Collector einigen Speicherplatz beibehält, da dieser sofort Ihrer Anwendung zugewiesen wird). Die freien Speicherblöcke sind nach wie vor committet, obwohl sie zurückgesetzt werden. Das bedeutet, dass das Betriebssystem in ihnen befindliche Daten nicht wieder auf den Datenträger schreiben muss.

Da der große Objektheap nur während Garbage Collections für Generation 2 bereinigt wird, kann ein Segment des großen Objektheaps nur während einer solchen Garbage Collection freigegeben werden. In Abbildung 3 wird ein Szenario veranschaulicht, indem der Garbage Collector ein Segment (Segment 2) für das Betriebssystem freigibt und mehr Speicherplatz für die verbleibenden Segmente aufhebt. Wenn der aufgehobene Speicherplatz am Ende des Segments verwendet werden muss, um Zuordnungsanforderungen für große Objekte zu erfüllen, wird der Arbeitsspeicher wieder committet. (Eine Erläuterung zum Übernehmen und Aufheben finden Sie in der Dokumentation für VirtualAlloc.)

Figure 3: LOH after a gen 2 GC
Abbildung 3: Der große Objektheap nach einer Garbage Collection für Generation 2

Wann wird ein großes Objekt bereinigt?

Im Allgemeinen wird eine Garbage Collection durchgeführt, wenn eine der folgenden drei Bedingungen zutrifft:

  • Die Zuordnung überschreitet den Schwellenwert von Generation 0 oder des großen Objekts.

    Der Schwellenwert ist eine Eigenschaft einer Generation. Ein Schwellenwert wird für eine Generation festgelegt, wenn der Garbage Collector diesem Objekte zuordnet. Wenn der Schwellenwert überschritten wird, wird eine Garbage Collection für diese Generation ausgelöst. Wenn Sie kleine oder große Objekte zuweisen, verwenden Sie daher die jeweiligen Schwellenwerte für Generation 0 und den großen Objektheap. Wenn der Garbage Collector eine Zuordnung zu Generation 1 oder 2 vornimmt, verwendet er deren Schwellenwerte. Diese Schwellenwerte werden während der Ausführung des Programms dynamisch abgestimmt.

    Dies ist der Normalfall. Die meisten Garbage Collections werden aufgrund von Zuordnungen im verwalteten Heap ausgelöst.

  • Die GC.Collect -Methode wird aufgerufen.

    Wenn die parameterlose GC.Collect()-Methode aufgerufen wird oder eine andere Überladung als Argument an GC.MaxGeneration übergeben wird, wird der große Objektheap zusammen mit dem restlichen verwalteten Heap bereinigt.

  • Das System verfügt über wenig Arbeitsspeicher.

    Dies tritt auf, wenn der Garbage Collector eine Benachrichtigung über eine hohe Arbeitsspeicherauslastung vom Betriebssystem erhält. Wenn der Garbage Collector eine Garbage Collection für Generation 2 für produktiv hält, wird diese ausgelöst.

Auswirkungen des LOH auf die Leistung

Zuordnungen zum großen Objektheap beeinträchtigen die Leistung folgendermaßen:

  • Zuordnungskosten:

    Die CLR garantiert, dass der Speicher für jedes neue Objekt, das herausgegeben wird, bereinigt wird. Dies bedeutet, dass die Zuordnungskosten für ein großes Objekt vollständig von der Speicherbereinigung dominiert werden (sofern keine Garbage Collection ausgelöst wird). Wenn zwei Zyklen für die Bereinigung von einem Byte benötigt werden, bedeutet dies, dass zum Bereinigen des kleinsten großen Objekts 170.000 Zyklen erforderlich sind. Das Bereinigen des Arbeitsspeichers eines 16 MB großen Objekts auf einem Computer mit 2 GHz dauert ungefähr 16 Millisekunden. Dies führt zu hohen Kosten.

  • Bereinigungskosten:

    Da der große Objektheap und Generation 2 zusammen bereinigt werden, wird eine Garbage Collection für Generation 2 ausgelöst, wenn einer der beiden Schwellenwerte überschritten wird. Wenn eine Garbage Collection für Generation 2 aufgrund des großen Objektheaps ausgelöst wurde, wird Generation 2 nach der Garbage Collection nicht unbedingt erheblich kleiner. Wenn nicht viele Daten zu Generation 2 gehören, hat dies nur minimale Auswirkungen. Ist Generation 2 jedoch groß, können Leistungsprobleme auftreten, wenn viele Garbage Collections für Generation 2 ausgelöst werden. Wenn viele große Objekte vorübergehend zugewiesen werden und Sie über einen großen kleinen Objektheap verfügen, verwenden Sie möglicherweise zu viel Zeit für Garbage Collections. Darüber hinaus können die Zuordnungskosten sich summieren, wenn Sie sehr große Objekte weiterhin zuordnen und freigeben.

  • Arrayelemente mit Verweistypen:

    Sehr große Objekte im großen Objektheap sind in der Regel Arrays (es kommt äußerst selten vor, dass ein sehr großes Instanzobjekt vorhanden ist). Wenn die Elemente eines Arrays viele Verweise enthalten, fallen Kosten an, die bei Elementen mit wenigen Verweise nicht anfallen. Enthält das Element keine Verweise, muss der Garbage Collector das Array überhaupt nicht durchlaufen. Wenn Sie beispielsweise ein Array zum Speichern von Knoten in einer binären Struktur verwenden, könnte dies implementiert werden, indem durch die tatsächlichen Knoten auf den rechten und linken Knoten eines Knotens verwiesen wird:

    class Node
    {
       Data d;
       Node left;
       Node right;
    };
    
    Node[] binary_tr = new Node [num_nodes];
    

    Wenn num_nodes groß ist, muss der Garbage Collector mindestens zwei Verweise pro Element durchlaufen. Ein alternativer Ansatz besteht darin, den Index des rechten und des linken Knotens zu speichern:

    class Node
    {
       Data d;
       uint left_index;
       uint right_index;
    } ;
    

    Verweisen Sie auf die Daten des linken Knotens nicht mit left.d, sondern mit binary_tr[left_index].d. Der Garbage Collector muss dann keine Verweise für den linken und rechten Knoten betrachten.

Von diesen drei Faktoren sind die ersten beiden üblicherweise relevanter als der dritte. Deshalb wird empfohlen, dass Sie einen Pool von großen Objekten zuordnen, den Sie wiederverwenden, statt temporäre Objekte zuzuordnen.

Sammeln von Leistungsdaten für den LOH

Bevor Sie Leistungsdaten für einen bestimmten Bereich erfassen, sollten Sie Folgendes durchgeführt haben:

  1. Beweise dafür suchen, dass Sie diesen Bereich überprüfen sollten
  2. Andere bekannte Bereiche ohne ein Ergebnis untersuchen, das das vorhandene Leistungsproblem erklären könnte

Weitere Informationen zu den Grundlagen zu Arbeitsspeicher und CPU finden Sie im Blogbeitrag Verstehen des Problems vor der Lösungsfindung.

Sie können folgende Tools verwenden, um Daten über die Leistung des großen Objektheaps zu erfassen:

.NET CLR-Speicherleistungsindikatoren

.NET CLR-Speicherleistungsindikatoren sind in der Regel ein guter erster Schritt bei der Untersuchung von Leistungsproblemen. Es wird jedoch empfohlen, ETW-Ereignisse (Ereignisablaufverfolgung für Windows) zu verwenden. Eine allgemeine Möglichkeit zum Anzeigen von Leistungsindikatoren ist die Verwendung des Systemmonitors (perfmon.exe). Wählen Sie Hinzufügen (STRG + A) aus, um die entsprechenden Indikatoren für Prozesse hinzuzufügen, die für Sie wichtig sind. Sie können die Leistungsindikatordaten in einer Protokolldatei speichern.

Die folgenden zwei Leistungsindikatoren in der Kategorie .NET CLR-Speicher sind für den Heap für große Objekte (Large Object Heap, LOH) relevant:

  • Anzahl von Gen 2-Sammlungen

    Zeigt an, wie viele Garbage Collections für Generation 2 durchgeführt wurden, seit der Prozess gestartet wurde. Der Indikator wird am Ende einer Garbage Collection für Generation 2 (auch als vollständige Garbage Collection bezeichnet) erhöht. Dieser Indikator zeigt den letzten erfassten Wert an.

  • Größe des Heap für große Objekte

    Zeigt die aktuelle Größe des großen Objektheaps in Byte (einschließlich des freien Speicherplatzes) an. Dieser Indikator wird nicht bei jeder Zuordnung, sondern nur am Ende einer Garbage Collection aktualisiert.

Screenshot that shows adding counters in Performance Monitor.

Sie können Leistungsindikatoren auch programmgesteuert mithilfe der PerformanceCounter-Klasse abfragen. Geben Sie für den LOH „.NET CLR-Speicher“ als CategoryName und „Objektheapgröße“ als CounterName an.

PerformanceCounter performanceCounter = new()
{
    CategoryName = ".NET CLR Memory",
    CounterName = "Large Object Heap size",
    InstanceName = "<instance_name>"
};

Console.WriteLine(performanceCounter.NextValue());

Es ist üblich, Indikatoren programmgesteuert als Teil eines routinemäßigen Testprozesses zu sammeln. Falls Sie Indikatoren mit ungewöhnlichen Werten bemerken, können Sie mithilfe anderer Methoden detailliertere Daten abgerufen werden, um die Untersuchung zu erleichtern.

Hinweis

Es wird empfohlen, ETW-Ereignisse statt Leistungsindikatoren zu verwenden, da ETW umfangreichere Informationen bereitstellt.

ETW-Ereignisse

Der Garbage Collector bietet viele ETW-Ereignisse, mit denen Sie besser nachvollziehen können, welche Aktionen der Heap durchführt und warum. In folgenden Blogbeiträgen wird das Erfassen und Verstehen von GC-Ereignissen mit ETW veranschaulicht:

Betrachten Sie die Spalte „Triggergrund“ für die Garbage Collections, um übermäßige Garbage Collections für Generation 2 zu identifizieren, die von temporären Zuordnungen von großen Objektheaps verursacht wurden. Wenn Sie einen einfachen Test durchführen möchten, der nur große Objekte temporär zuordnet, können Sie mithilfe des folgenden PerfView-Befehls Informationen zu ETW-Ereignissen erfassen:

perfview /GCCollectOnly /AcceptEULA /nogui collect

Das Ergebnis ähnelt Folgendem:

Screenshot that shows ETW events in PerfView.

Sie werden feststellen, dass alle Garbage Collections für Generation 2 durchgeführt und von AllocLarge ausgelöst wurden. Das bedeutet, dass die Zuordnung eines großen Objekts diese Garbage Collection ausgelöst hat. Es ist bekannt, dass es sich um temporäre Zuordnungen handelt, da die Spalte LOH Survival Rate (Beibehaltungsrate des großen Objektheaps) „1 %“ anzeigt.

Sie können zusätzliche ETW-Ereignisse erfassen, aus denen hervorgeht, wer diese großen Objekte zugeordnet hat. Verwenden Sie beispielsweise folgende Befehlszeile:

perfview /GCOnly /AcceptEULA /nogui collect

Dadurch wird ein AllocationTick-Ereignis erfasst, das etwa alle 100.000 Zuordnungen ausgelöst wird. Das bedeutet, dass immer dann ein Ereignis ausgelöst wird, wenn ein großes Objekt zugeordnet wird. Sie können dann eine der Ansichten für die Heapzuordnung der Garbage Collection betrachten, in denen die Aufruflisten angezeigt werden, die große Objekte zugeordnet haben:

Screenshot that shows a garbage collector heap view.

Wie Sie sehen können, handelt es sich hierbei um einen einfachen Test, der nur große Objekte aus seiner Main-Methode zuordnet.

Debugger

Wenn Sie nur ein Speicherabbild besitzen und untersuchen müssen, welche Objekte sich im großen Objektheap befinden, können Sie die von .NET bereitgestellte SOS-Debugerweiterung verwenden.

Hinweis

Die in diesem Abschnitt erwähnten Debuggingbefehle gelten für die Windows-Debugger.

Nachfolgend wird eine Beispielausgabe für die Analyse des großen Objektheaps angezeigt:

0:003> .loadby sos mscorwks
0:003> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x013e35ec
sdgeneration 1 starts at 0x013e1b6c
generation 2 starts at 0x013e1000
ephemeral segment allocation context: none
segment   begin allocated     size
0018f2d0 790d5588 790f4b38 0x0001f5b0(128432)
013e0000 013e1000 013e35f8 0x000025f8(9720)
Large object heap starts at 0x023e1000
segment   begin allocated     size
023e0000 023e1000 033db630 0x00ffa630(16754224)
033e0000 033e1000 043cdf98 0x00fecf98(16699288)
043e0000 043e1000 05368b58 0x00f87b58(16284504)
Total Size 0x2f90cc8(49876168)
------------------------------
GC Heap Size 0x2f90cc8(49876168)
0:003> !dumpheap -stat 023e1000 033db630
total 133 objects
Statistics:
MT   Count   TotalSize Class Name
001521d0       66     2081792     Free
7912273c       63     6663696 System.Byte[]
7912254c       4     8008736 System.Object[]
Total 133 objects

Die Größe des großen Objektheaps beträgt 49.738.016 Byte (16.754.224 + 16.699.288 + 16.284.504). Zwischen den Adressen 023e1000 und 033db630 sind 8.008.736 Byte von einem Array von System.Object-Objekten und 6.663.696 Byte sind von einem Array von System.Byte-Objekten belegt. 2.081.792 Byte sind von freiem Speicherplatz belegt.

Manchmal zeigt der Debugger an, dass die Gesamtgröße des großen Objektheaps weniger als 85.000 Byte beträgt. Dies geschieht, weil die Runtime den großen Objektheap zur Zuordnung von bestimmten Objekten verwendet, die kleiner als ein großes Objekt sind.

Da der große Objektheap nicht komprimiert wird, wird der große Objektheap manchmal für die Quelle der Fragmentierung gehalten. Fragmentierung bedeutet:

  • Die Fragmentierung des verwalteten Heaps, die sich durch die Menge des freien Speicherplatzes zwischen verwalteten Objekten auszeichnet. Im SOS-Debugger zeigt der !dumpheap –type Free-Befehl die Menge des freien Speicherplatzes zwischen verwalteten Objekten an.

  • Die Fragmentierung des Adressraums des virtuellen Arbeitsspeichers, bei dem es sich um den Arbeitsspeicher handelt, der als MEM_FREE markiert ist. Sie können diesen mithilfe von verschiedenen Debuggerbefehlen in WinDbg abrufen.

    Im folgenden Beispiel wird die Fragmentierung des virtuellen Arbeitsspeichers dargestellt:

    0:000> !address
    00000000 : 00000000 - 00010000
    Type     00000000
    Protect 00000001 PAGE_NOACCESS
    State   00010000 MEM_FREE
    Usage   RegionUsageFree
    00010000 : 00010000 - 00002000
    Type     00020000 MEM_PRIVATE
    Protect 00000004 PAGE_READWRITE
    State   00001000 MEM_COMMIT
    Usage   RegionUsageEnvironmentBlock
    00012000 : 00012000 - 0000e000
    Type     00000000
    Protect 00000001 PAGE_NOACCESS
    State   00010000 MEM_FREE
    Usage   RegionUsageFree
    … [omitted]
    -------------------- Usage SUMMARY --------------------------
    TotSize (     KB)   Pct(Tots) Pct(Busy)   Usage
    701000 (   7172) : 00.34%   20.69%   : RegionUsageIsVAD
    7de15000 ( 2062420) : 98.35%   00.00%   : RegionUsageFree
    1452000 (   20808) : 00.99%   60.02%   : RegionUsageImage
    300000 (   3072) : 00.15%   08.86%   : RegionUsageStack
    3000 (     12) : 00.00%   00.03%   : RegionUsageTeb
    381000 (   3588) : 00.17%   10.35%   : RegionUsageHeap
    0 (       0) : 00.00%   00.00%   : RegionUsagePageHeap
    1000 (       4) : 00.00%   00.01%   : RegionUsagePeb
    1000 (       4) : 00.00%   00.01%   : RegionUsageProcessParametrs
    2000 (       8) : 00.00%   00.02%   : RegionUsageEnvironmentBlock
    Tot: 7fff0000 (2097088 KB) Busy: 021db000 (34668 KB)
    
    -------------------- Type SUMMARY --------------------------
    TotSize (     KB)   Pct(Tots) Usage
    7de15000 ( 2062420) : 98.35%   : <free>
    1452000 (   20808) : 00.99%   : MEM_IMAGE
    69f000 (   6780) : 00.32%   : MEM_MAPPED
    6ea000 (   7080) : 00.34%   : MEM_PRIVATE
    
    -------------------- State SUMMARY --------------------------
    TotSize (     KB)   Pct(Tots) Usage
    1a58000 (   26976) : 01.29%   : MEM_COMMIT
    7de15000 ( 2062420) : 98.35%   : MEM_FREE
    783000 (   7692) : 00.37%   : MEM_RESERVE
    
    Largest free region: Base 01432000 - Size 707ee000 (1843128 KB)
    

Häufiger kommt es vor, dass eine Fragmentierung des virtuellen Arbeitsspeichers durch temporäre große Objekte verursacht wird, für die erforderlich ist, dass der Garbage Collector regelmäßig neue verwaltete Heapsegmente vom Betriebssystem abruft und leere Segmente wieder für das Betriebssystem freigibt.

Sie können überprüfen, ob der große Objektheap die Fragmentierung des virtuellen Arbeitsspeichers verursacht, indem Sie einen Breakpoint für VirtualAlloc und VirtualFree festlegen und bestimmen, von wem diese Funktionen aufgerufen werden. Sie können einen Breakpoint z. B. folgendermaßen festlegen, um anzuzeigen, wer versucht hat, virtuelle Arbeitsspeicherblöcke, die größer als 8 MB sind, aus dem Betriebssystem zuzuordnen:

bp kernel32!virtualalloc "j (dwo(@esp+8)>800000) 'kb';'g'"

Dieser Befehl unterbricht den Debugger und zeigt die Aufrufliste nur an, wenn VirtualAlloc mit einer Zuordnungsgröße aufgerufen wird, die größer als 8 MB (0x800000) ist.

In CLR 2.0 wurde ein Feature namens VM Hoarding hinzugefügt, das für Szenarios nützlich ist, in denen Segmente (einschließlich des großen und des kleinen Objektheaps) häufig abgerufen und freigegeben werden. Wenn Sie das Feature „VM Hoarding“ definieren möchten, geben Sie ein Startflag namens STARTUP_HOARD_GC_VM über die Hosting-API an. Die CLR hebt den Arbeitsspeicher für diese Segmente auf, und setzt diese auf eine Standbyliste, anstatt leere Segmente wieder für das Betriebssystem freizugeben. (Beachten Sie, dass die CLR dies nicht für Segmente durchführen kann, die zu groß sind.) Die CLR verwendet diese Segmente später, um Anforderungen für neue Segmente zu erfüllen. Wenn Ihre App das nächste Mal ein neues Segment benötigt, verwendet die CLR eines von dieser Standbyliste, wenn sie eines finden kann, das groß genug ist.

Die Funktion „VM Hoarding“ ist ebenfalls für Anwendungen nützlich, in denen bereits abgerufene Segmente beibehalten werden sollen. Dazu zählen beispielsweise Server-Apps, die als dominante Apps im System ausgeführt werden, um Ausnahmen wegen nicht ausreichendem Arbeitsspeicher zu vermeiden.

Es wird nachdrücklich empfohlen, dass Sie Ihre Anwendung sorgfältig testen, wenn Sie dieses Feature verwenden, und sicherstellen, dass die Speicherauslastung Ihrer Anwendung relativ stabil ist.