Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Der .NET Garbage Collector (GC) teilt Objekte in kleine und große Objekte auf. Wenn ein Objekt groß ist, werden einige seiner Attribute wichtiger, als wenn das Objekt klein ist. 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, was ein Objekt als großes Objekt qualifiziert, wie große Objekte gesammelt werden und welche Art von Leistungsauswirkungen große Objekte auferlegen.
Von Bedeutung
In diesem Artikel werden der Large Object Heap im .NET Framework und .NET Core besprochen, die nur auf Windows-Systemen laufen. Es deckt nicht die LOH ab, die auf .NET-Implementierungen auf anderen Plattformen ausgeführt wird.
Wie ein Objekt am LOH endet
Wenn ein Objekt größer oder gleich 85.000 Bytes ist, wird es als großes Objekt betrachtet. Diese Zahl wurde durch leistungsoptimierung bestimmt. Wenn eine Objektzuweisungsanforderung 85.000 oder mehr Bytes umfasst, weist die Laufzeit es dem großen Objekt-Heap zu.
Um zu verstehen, was dies bedeutet, ist es hilfreich, einige Grundlagen zum Müllsammler zu betrachten.
Der Garbage Collector ist ein Generationensammler. Es hat drei Generationen: Generation 0, Generation 1 und Generation 2. Der Grund für drei Generationen ist, dass die meisten Objekte in einer gut abgestimmten App in Generation 0 sterben. 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 Wesentlichen fungiert gen1 als Puffer zwischen jungen Objektbereichen und langlebigen Objektbereichen.
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, gehen sie auf den Large Object Heap (LOH), der manchmal als Generation 3 bezeichnet wird. Generation 3 ist eine physische Generation, die logisch als Teil der Generation 2 gesammelt wird.
Große Objekte gehören zur Generation 2, da sie nur während einer Sammlung der Generation 2 gesammelt werden. Wenn eine Generation gesammelt wird, werden auch alle jüngeren Generationen gesammelt. Wenn beispielsweise ein Generation 1 GC-Ereignis eintritt, werden sowohl Generation 1 als auch Generation 0 gesammelt. Erfolgt eine Garbage Collection für Generation 2, wird der gesamte Heap bereinigt. Aus diesem Grund wird eine GC-Generation der Generation 2 auch als vollständiges GC bezeichnet. Dieser Artikel bezieht sich auf Generation 2 GC anstelle von vollständigem GC, aber die Begriffe sind austauschbar.
Die Generationen stellen eine logische Ansicht des Garbage Collection-Heaps bereit. Physisch gesehen werden Objekte in verwalteten Heapsegmenten gespeichert. Ein verwaltetes Heapsegment ist ein Speicherabschnitt, den die GC vom Betriebssystem reserviert, indem die VirtualAlloc-Funktion im Auftrag von verwaltetem Code aufgerufen wird. 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 platziert werden. Wenn das Objekt kleiner als 85.000 Byte ist, wird es auf das Segment für das SOH gesetzt. andernfalls wird es auf ein LOH-Segment gesetzt. Segmente werden zugesichert (in kleineren Blöcken), da ihnen immer mehr Objekte zugeordnet werden. Für die SOH werden Objekte, die eine GC überleben, zur nächsten Generation heraufgestuft. Objekte, die eine Sammlung der Generation 0 überleben, gelten jetzt als Objekte der Generation 1 usw. Objekte, die die älteste Generation überleben, gelten jedoch noch als in der ältesten Generation. Mit anderen Worten: Überlebende der Generation 2 sind Objekte der Generation 2; und Überlebende des LOH sind LOH-Objekte (die mit gen2 gesammelt werden).
Benutzercode kann nur in der Generation 0 (kleine Objekte) oder im großen Objektheap (große Objekte) zugeordnet werden. Nur die GC kann Objekte der Generation 1 (durch Förderung von Überlebenden von Generation 0) und Generation 2 (durch Förderung von Überlebenden der Generation 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. Benachbarte tote Objekte werden in ein 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. Und in Zukunft kann .NET entscheiden, die LOH automatisch zu komprimieren. Dies bedeutet: Wenn Sie große Objekte zuordnen und sicherstellen möchten, dass sie nicht verschoben werden, sollten Sie sie trotzdem anheften.
Abbildung 1 veranschaulicht ein Szenario, in dem die Generation 1 der GC nach der ersten Generation 0 GC gebildet wird, bei der Obj1
und Obj3
tot sind, und Generation 2 nach der ersten Generation 1 GC gebildet wird, bei der Obj2
und Obj5
tot sind. Beachten Sie, dass dies und die folgenden Abbildungen nur zu Veranschaulichungszwecken dienen; sie enthalten sehr wenige Objekte, um besser zu zeigen, was auf dem Heap passiert. In Wirklichkeit sind viele weitere Objekte in der Regel an einer GC beteiligt.
Abbildung 1: Eine Generation 0 und eine Generation 1 GC.
Abbildung 2 zeigt, dass nach einer GC der Generation 2, die feststellte, dass Obj1
und Obj2
nicht mehr verwendet werden, die GC zusammenhängenden freien Speicherplatz aus dem zuvor von Obj1
und Obj2
belegten Speicher bildet, der dann genutzt wurde, um eine Zuordnungsanforderung für Obj4
zu erfüllen. Der Abstand nach dem letzten Objekt Obj3
, bis zum Ende des Abschnitts kann auch verwendet werden, um Zuordnungsanforderungen zu erfüllen.
Abbildung 2: Nach einer Garbage Collection für Generation 2
Wenn nicht genügend freier Speicherplatz vorhanden ist, um die Anforderungen für die große Objektzuordnung aufzunehmen, versucht die GC zunächst, weitere Segmente aus dem Betriebssystem zu erwerben. 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). Und die freien Speicherplätze bleiben bestehen, obwohl sie zurückgesetzt werden, was bedeutet, dass das Betriebssystem keine Daten 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. Abbildung 3 zeigt ein Szenario, in dem der Garbage Collector ein Segment (Segment 2) an das Betriebssystem zurückgibt und mehr Speicher auf den verbleibenden Segmenten freigibt. 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 von Commit/Decommit finden Sie in der Dokumentation zu VirtualAlloc.)
Abbildung 3: Der große Objektheap nach einer Garbage Collection für Generation 2
Wann wird ein großes Objekt gesammelt?
Im Allgemeinen tritt eine GC unter einer der folgenden drei Bedingungen auf:
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 ein GC 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 dynamisch abgestimmt, wenn das Programm ausgeführt wird.
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 befindet sich in einer Speichermangelsituation.
Dies tritt auf, wenn der Garbage Collector eine Benachrichtigung über hohen Arbeitsspeicher vom Betriebssystem empfängt. 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 es zwei Zyklen dauert, um ein Byte zu löschen, dauert es 170.000 Zyklen, um das kleinste große Objekt zu löschen. Das Löschen des Speichers eines 16-MB-Objekts auf einem 2-GHz-Computer erfordert ca. 16 ms. Das sind ziemlich hohe Kosten.
Sammlungskosten.
Da der LOH und die Generation 2 gemeinsam gesammelt werden, wird eine Erfassung der zweiten Generation ausgelöst, wenn der Schwellenwert einer der beiden ü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 es nicht viele Daten zur Generation 2 gibt, hat dies minimale Auswirkungen. Wenn die Generation 2 jedoch groß ist, kann sie Leistungsprobleme verursachen, wenn viele GCs der 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 auf dem LOH sind in der Regel Arrays (es ist sehr selten, ein Instanzobjekt zu haben, das wirklich groß ist). Wenn die Elemente eines Arrays viele Referenzen enthalten, entstehen Kosten, die bei Elementen ohne viele Referenzen nicht auftreten. Wenn das Element keine Verweise enthält, 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 der rechten und linken Knoten zu speichern:class Node { Data d; uint left_index; uint right_index; } ;
Anstatt auf die Daten des linken Knotens zu verweisen als
left.d
, verweisen Sie darauf.binary_tr[left_index].d
Der Garbage Collector muss dann keine Verweise für den linken und rechten Knoten betrachten.
Aus den drei Faktoren sind die ersten beiden in der Regel wichtiger als der dritte. Aus diesem Gründen wird empfohlen, einen Pool mit großen Objekten zuzuweisen, die Sie wiederverwenden, anstatt temporäre Objekte zuzuweisen.
Sammeln von Leistungsdaten für den LOH
Bevor Sie Leistungsdaten für einen bestimmten Bereich sammeln, sollten Sie bereits Folgendes getan haben:
- Wir haben Nachweise gefunden, die darauf hinweisen, dass Sie diesen Bereich genauer betrachten sollten.
- Andere bekannte Bereiche ohne ein Ergebnis untersuchen, das das vorhandene Leistungsproblem erklären könnte
Weitere Informationen zu den Grundlagen des Arbeitsspeichers und der CPU finden Sie im Blog "Grundlegendes zum Problem", bevor Sie versuchen, eine Lösung zu finden.
Mit den folgenden Tools können Sie Daten zur LOH-Leistung sammeln:
.NET CLR Speicherleistungsindikatoren
.NET CLR Speicherleistungsindikatoren sind in der Regel ein guter erster Schritt bei der Untersuchung von Leistungsproblemen (obwohl wir empfehlen, ETW-Ereignisse zu verwenden). Eine gängige Möglichkeit zum Betrachten von Leistungsindikatoren ist der Performance Monitor (perfmon.exe). Wählen Sie "Hinzufügen " (STRG + A) aus, um die interessanten Indikatoren für Prozesse hinzuzufügen, die Sie interessieren. 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:
# Gen 2 Collections
Zeigt an, wie oft Generation 2 GCs seit dem Start des Prozesses aufgetreten sind. Der Indikator wird am Ende einer Garbage Collection für Generation 2 (auch als vollständige Garbage Collection bezeichnet) erhöht. Dieser Zähler zeigt den letzten beobachteten Wert an.
Größe des Heap für große Objekte
Zeigt die aktuelle Größe in Byte an, einschließlich freier Speicherplatz, des LOH. Dieser Indikator wird nicht bei jeder Zuordnung, sondern nur am Ende einer Garbage Collection aktualisiert.
Sie können leistungsindikatoren auch programmgesteuert mithilfe der PerformanceCounter Klasse abfragen. Geben Sie für den LOH ".NET CLR Memory" als CategoryName und "Large Object Heap size" 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, Zähler programmgesteuert als Teil eines Routinetestprozesses zu sammeln. Wenn Sie Indikatoren mit Werten erkennen, die nicht gewöhnlich sind, verwenden Sie andere Mittel, um detailliertere Daten zu erhalten, um die Untersuchung zu unterstützen.
Hinweis
Es wird empfohlen, ETW-Ereignisse anstelle von Leistungsindikatoren zu verwenden, da ETW viel 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 den folgenden Blogbeiträgen wird gezeigt, wie GC-Ereignisse mit ETW gesammelt und verstanden werden:
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. Für einen einfachen Test, der nur temporäre große Objekte zuordnet, können Sie Informationen zu ETW-Ereignissen mit dem folgenden PerfView-Befehl sammeln:
perfview /GCCollectOnly /AcceptEULA /nogui collect
Das Ergebnis sieht ungefähr wie folgt aus:
Wie Sie sehen können, sind alle GCs der Generation 2, und sie werden alle durch AllocLarge ausgelöst, was bedeutet, dass die Zuordnung eines großen Objekts diesen GC ausgelöst hat. Wir wissen, dass diese Zuordnungen vorübergehend sind, da die LOH-Überlebensrate % Spalte 1%sagt.
Sie können zusätzliche ETW-Ereignisse sammeln, die Ihnen mitteilen, 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. Mit anderen Worten, ein Ereignis wird jedes Mal ausgelöst, 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:
Wie Sie sehen können, ist dies ein sehr einfacher Test, der nur große Objekte aus seiner Main
Methode zuordnet.
Debugger
Wenn Sie nur über ein Speicherabbild verfügen und sich ansehen müssen, welche Objekte tatsächlich auf dem LOH enthalten sind, können Sie die von .NET bereitgestellte SoS-Debuggererweiterung verwenden.
Hinweis
Die in diesem Abschnitt erwähnten Debugbefehle 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 Adressen 023e1000 und 033db630 werden 8.008.736 Bytes von einem Array von System.Object Objekten belegt, 6.663.696 Byte werden von einem Array von System.Byte Objekten belegt, und 2.081.792 Bytes werden durch freien Speicherplatz belegt.
Manchmal zeigt der Debugger an, dass die Gesamtgröße des LOH kleiner als 85.000 Bytes ist. Dies geschieht, da die Laufzeit selbst den LOH verwendet, um einige Objekte zuzuordnen, die kleiner als ein großes Objekt sind.
Da die LOH nicht komprimiert ist, wird manchmal der LOH als Quelle der Fragmentierung angesehen. Fragmentierung bedeutet:
Die Fragmentierung des verwalteten Heaps, die sich durch die Menge des freien Speicherplatzes zwischen verwalteten Objekten auszeichnet. In SoS zeigt der
!dumpheap –type Free
Befehl den freien Speicherplatz 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 es mit verschiedenen Debuggerbefehlen in windbg abrufen.Das folgende Beispiel zeigt die Fragmentierung im Bereich der virtuellen Maschine.
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)
Es kommt häufiger vor, dass die VM-Fragmentierung durch temporäre große Objekte verursacht wird, die den Garbage Collector dazu bringen, häufig neue verwaltete Heapsegmente vom Betriebssystem zu erwerben und leere wieder an das Betriebssystem zurückzugeben.
Um zu überprüfen, ob der LOH eine VM-Fragmentierung verursacht, können Sie einen Haltepunkt für VirtualAlloc und VirtualFree festlegen, um zu sehen, wer sie aufgerufen hat. 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 wechselt in den Debugger und zeigt den Aufrufstapel nur an, wenn VirtualAlloc mit einer Zuordnungsgröße von mehr als 8 MB (0x800000) aufgerufen wird.
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. Um VM Hoarding anzugeben, geben Sie über die Hosting-API das Startflag STARTUP_HOARD_GC_VM
an. Anstatt leere Segmente wieder an das Betriebssystem freizugeben, setzt die CLR den Arbeitsspeicher in diesen Segmenten zurück und stellt ihn auf eine Warteliste. (Beachten Sie, dass die CLR dies nicht für Segmente tut, die zu groß sind.) Die CLR verwendet diese Segmente später, um neue Segmentanforderungen zu erfüllen. Wenn Ihre App das nächste Mal ein neues Segment benötigt, verwendet die CLR eins aus dieser Standbyliste, wenn sie eins 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 dringend empfohlen, Ihre Anwendung sorgfältig zu testen, wenn Sie dieses Feature verwenden, um sicherzustellen, dass Ihre Anwendung über eine relativ stabile Speicherauslastung verfügt.