Udostępnij za pośrednictwem


Sterta dużych obiektów w systemach Windows

Moduł odśmiecania pamięci platformy .NET (GC) dzieli obiekty na małe oraz duże. Gdy obiekt jest duży, niektóre z jego atrybutów stają się bardziej znaczące niż jeśli obiekt jest mały. Na przykład kompaktowanie — czyli kopiowanie go w pamięci w innym miejscu sterty — może być kosztowne. W związku z tym moduł odśmiecenia pamięci umieszcza duże obiekty na stercie dużych obiektów (LOH). W tym artykule omówiono, co kwalifikuje obiekt jako duży obiekt, jak duże obiekty są zbierane i jaki wpływ na wydajność nakładają duże obiekty.

Ważne

W tym artykule omówiono stertę dużych obiektów w programach .NET Framework i .NET Core działających tylko w systemach Windows. Nie obejmuje ona pakietu LOH uruchomionego na implementacjach platformy .NET na innych platformach.

W jaki sposób obiekt trafia na LOH

Jeśli obiekt jest większy lub równy 85 000 bajtów w rozmiarze, jest uważany za duży obiekt. Ta liczba została określona przez dostrajanie wydajności. Gdy żądanie alokacji obiektu dotyczy co najmniej 85 000 bajtów, mechanizm uruchomieniowy przydziela je na stercie dużych obiektów.

Aby zrozumieć, co to oznacza, warto zbadać pewne podstawy dotyczące modułu odśmiecającego śmieci.

Odśmiecacz wykorzystuje generacyjne zbieranie. Ma trzy pokolenia: generację 0, generację 1 i generację 2. Powodem posiadania trzech pokoleń jest to, że w dobrze dostosowanej aplikacji większość obiektów umiera w generacji0. Na przykład w aplikacji serwera alokacje skojarzone z każdym żądaniem powinny umrzeć po zakończeniu żądania. Żądania alokacji w locie sprawią, że zostaną wprowadzone do generacji1 i tam umrze. Zasadniczo gen1 działa jako bufor między młodymi obszarami obiektów a obszarami obiektów o długim czasie życia.

Nowo przydzielone obiekty tworzą nową generację obiektów i są niejawnie generacji kolekcji 0. Jednak jeśli są dużymi obiektami, znajdują się w dużej stercie obiektów (LOH), która jest czasami określana jako generacja 3. Generacja 3 to generacja fizyczna, która jest logicznie zbierana w ramach generacji 2.

Duże obiekty należą do generacji 2, ponieważ są zbierane tylko podczas kolekcji generacji 2. Po zebraniu jednego pokolenia, wszystkie jego młodsze pokolenia również są zbierane. Na przykład gdy wystąpi generacja 1 GC, zbierane są obie generacje 1 i 0. A kiedy następuje generacja 2 GC, cała sterta jest usuwana. Z tego powodu generacja 2 GC jest również nazywana pełną GC. Ten artykuł odnosi się do generacji 2 GC zamiast pełnej GC, ale terminy są zamienne.

Generacje zapewniają logiczny widok stosu pamięci GC. Fizycznie obiekty znajdują się w zarządzanych segmentach stert. Segment sterty zarządzanej to fragment pamięci zarezerwowany przez system operacyjny przez wywołanie funkcji VirtualAlloc w imieniu kodu zarządzanego. Po załadowaniu CLR, GC przydziela dwa początkowe segmenty sterty: jeden dla małej sterty obiektów (SOH), i jeden dla dużej sterty obiektów.

Żądania alokacji są następnie spełnione przez umieszczenie obiektów zarządzanych w tych zarządzanych segmentach sterty. Jeśli obiekt jest mniejszy niż 85 000 bajtów, jest umieszczany w segmencie SOH; w przeciwnym razie jest umieszczany na segmencie LOH. Segmenty są zatwierdzane stopniowo (w mniejszych fragmentach) w miarę jak coraz więcej obiektów jest do nich przydzielanych. W przypadku SOH obiekty, które przetrwają GC, są promowane do następnej generacji. Obiekty, które przetrwają kolekcję generacji 0, są teraz traktowane jako obiekty generacji 1 itd. Jednak obiekty, które przetrwają w najstarszym pokoleniu, są nadal uważane za część najstarszego pokolenia. Innymi słowy, obiekty ocalałe z generacji 2 to obiekty generacji 2; a obiekty ocalałe z LOH to obiekty LOH (które są zbierane razem z generacją 2).

Kod użytkownika może przydzielić tylko generacji 0 (małe obiekty) lub LOH (duże obiekty). Tylko GC może "przydzielić" obiekty w generacji 1 (poprzez promowanie ocalałych z pokolenia 0) i pokolenia 2 (poprzez promowanie ocalałych z pokolenia 1).

Po uruchomieniu odśmiecania pamięci funkcja GC śledzi obiekty na żywo i optymalizuje ich ułożenie. Ale ponieważ kompaktowanie jest drogie, GC zamiata LOH; Tworzy wolną listę z niewykorzystanych obiektów, które można później użyć ponownie, aby zaspokoić zapotrzebowanie na alokację dużych obiektów. Sąsiadujące martwe obiekty są łączone w jeden wolny obiekt.

.NET Core i .NET Framework (począwszy od programu .NET Framework 4.5.1) zawierają GCSettings.LargeObjectHeapCompactionMode właściwość, która umożliwia użytkownikom określenie, że LOH powinny być kompaktowane podczas następnej pełnej blokady GC. A w przyszłości platforma .NET może zdecydować się na automatyczne kompaktowanie LOH. Oznacza to, że jeśli przydzielisz duże obiekty i chcesz upewnić się, że nie zostaną przeniesione, nadal należy je przypiąć.

Rysunek 1 ilustruje scenariusz, w którym GC tworzy generację 1 po pierwszej generacji 0 GC, gdzie Obj1 i Obj3 są martwe, i tworzy generację 2 po pierwszej generacji 1 GC, gdzie Obj2 i Obj5 są martwe. Należy pamiętać, że te i poniższe ilustracje są przeznaczone tylko do celów ilustracyjnych; zawierają bardzo niewiele obiektów, aby lepiej pokazać, co się dzieje na stercie. W rzeczywistości wiele innych obiektów jest zwykle zaangażowanych w GC.

Rysunek 1. Gen 0 GC i gen 1 GC
Rysunek 1. Generacja 0 i generacja 1 GC.

Rysunek 2 pokazuje, że po generacji 2, kiedy GC stwierdzi, że Obj1 i Obj2 są martwe, GC tworzy ciągłą wolną przestrzeń z pamięci, która wcześniej była zajmowana przez Obj1 i Obj2, a potem wykorzystuje ją do spełnienia żądania alokacji dla Obj4. Miejsce po ostatnim obiekcie, Obj3, aż do końca segmentu może być również używane do zaspokojenia żądań alokacji.

Rysunek 2. Po generacji 2 GC
Rysunek 2. Po generacji 2 GC

Jeśli nie ma wystarczającej ilości wolnego miejsca do obsługi dużych żądań alokacji obiektów, GC najpierw próbuje uzyskać więcej segmentów z systemu operacyjnego. Jeśli to się nie powiedzie, wyzwala generację 2 GC w nadziei na zwolnienie miejsca.

W generacji 1 lub 2 GC moduł odśmiecania pamięci zwalnia segmenty, które nie zawierają żywych obiektów, do systemu operacyjnego, wywołując funkcję VirtualFree. Przestrzeń po ostatnim obiekcie aktywnym na końcu segmentu jest odłączona (z wyjątkiem segmentu efemerycznego, w którym istnieje gen0/gen1, gdzie mechanizm odśmiecania pamięci zachowuje część zatwierdzoną, ponieważ aplikacja będzie w nim natychmiast dokonywać alokacji). Wolne miejsca pozostają zatwierdzone, chociaż są resetowane, co oznacza, że system operacyjny nie musi zapisywać danych z powrotem na dysku.

Ponieważ LOH jest zbierane tylko podczas generacji 2 GC, segment LOH można uwolnić tylko podczas takiego GC. Rysunek 3 przedstawia scenariusz, w którym moduł odśmiecania pamięci zwalnia jeden segment (segment 2) z powrotem do systemu operacyjnego i zwalnia więcej miejsca w pozostałych segmentach. Jeśli trzeba będzie użyć zwolnionej przestrzeni na końcu segmentu do spełnienia żądań alokacji dużych obiektów, pamięć zostanie ponownie zatwierdzona. (Aby uzyskać wyjaśnienie zatwierdzenia/dealokacji, sprawdź dokumentację VirtualAlloc).

Rysunek 3. LOH po generacji 2 GC
Rysunek 3: LOH po generacji 2 GC

Kiedy jest odbierany duży przedmiot?

Ogólnie rzecz biorąc, GC występuje w jednym z następujących trzech okoliczności.

  • Alokacja przekracza próg dla generacji 0 lub dużego obiektu.

    Próg jest właściwością generacji. Próg generowania jest ustawiany, gdy moduł odśmiecanie pamięci przydziela do niego obiekty. Po przekroczeniu progu zostanie wyzwolony GC dla tej generacji. Gdy przydzielasz małe lub duże obiekty, zużywasz odpowiednio progi generacji 0 i LOH (Duży Obszar Sterty). Gdy kolektor śmieci alokuje zasoby w generacjach 1 i 2, zużywa ich progi. Te progi są dynamicznie dostrojone w miarę uruchamiania programu.

    Jest to typowy przypadek; większość kolektorów śmieci zdarza się z powodu alokacji na zarządzanej stercie.

  • Wywoływana jest metoda GC.Collect.

    Jeśli metoda bez GC.Collect() parametrów zostanie wywołana lub inne przeciążenie zostanie przekazane GC.MaxGeneration jako argument, LOH zostanie zebrana wraz z resztą zarządzanej sterty.

  • System jest w sytuacji niskiej pamięci.

    Dzieje się tak, gdy kolektor śmieci odbiera powiadomienie o wysokim zużyciu pamięci z systemu operacyjnego. Jeśli zgarniacz pamięci uważa, że wykonanie drugiego poziomu GC będzie produktywne, i wyzwala go.

Implikacje dotyczące wydajności LOH

Alokacje na stercie dużych obiektów wpływają na wydajność w następujący sposób.

  • Koszt alokacji.

    CLR gwarantuje, że pamięć dla każdego nowego obiektu, który jest tworzony, jest czyszczona. Oznacza to, że koszt alokacji dużego obiektu jest zdominowany przez czyszczenie pamięci (chyba że wyzwala GC). Jeśli potrzeba dwóch cykli, aby wyczyścić jeden bajt, potrzeba 170 000 cykli, aby wyczyścić najmniejszy duży obiekt. Wyczyszczenie pamięci obiektu 16 MB na maszynie 2-GHz zajmuje około 16 ms. To dość duży koszt.

  • Koszt windykacji.

    Ponieważ LOH i druga generacja są zbierane razem, jeśli zostanie przekroczony próg którejkolwiek z nich, następuje zebranie drugiej generacji. Jeśli kolekcja generacji 2 zostanie wyzwolona z powodu LOH, generacja 2 nie musi być znacznie mniejsza po GC. Jeśli nie ma zbyt dużej ilości danych na generację 2, ma to minimalny wpływ. Jeśli jednak generacja 2 jest duża, może to spowodować problemy z wydajnością, jeśli wyzwolono wiele kontrolerów generacji 2. Jeśli wiele dużych obiektów jest przydzielanych na zasadzie tymczasowej i masz dużą wartość SOH, możesz poświęcić zbyt dużo czasu na wykonywanie kontrolerów domeny. Ponadto koszt alokacji może się naprawdę sumować jeśli nadal alokujesz i zwalniasz naprawdę duże obiekty.

  • Elementy tablicy z typami referencyjnymi.

    Bardzo duże obiekty na LOH to zazwyczaj tablice (bardzo rzadko zdarza się, aby obiekt instancji był naprawdę duży). Jeśli elementy tablicy są bogate w odwołania, wiąże się to z pewnym kosztem, który nie dotyczy elementów niezawierających wielu odwołań. Jeśli element nie zawiera żadnych odwołań, moduł odśmiecający pamięci nie musi w ogóle przechodzić przez tablicę. Jeśli na przykład używasz tablicy do przechowywania węzłów w drzewie binarnym, jednym ze sposobów jej zaimplementowania jest odwoływanie się do prawego i lewego węzła za pomocą rzeczywistych węzłów:

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

    Jeśli num_nodes jest duży, zbiersz śmieci musi przejrzeć co najmniej dwa odwołania na element. Alternatywną metodą jest przechowywanie indeksu dla prawego i lewego węzła.

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

    Zamiast odwoływać się do danych lewego węzła jako left.d, należy je odwoływać jako binary_tr[left_index].d. System odśmiecający nie musi przeglądać żadnych referencji dla lewego i prawego węzła.

Spośród trzech czynników pierwsze dwa są zwykle bardziej znaczące niż trzeci. W związku z tym zalecamy przydzielenie puli dużych obiektów, które można używać ponownie, zamiast przydzielać tymczasowe.

Zbierz dane dotyczące wydajności dla LOH

Przed zebraniu danych wydajności dla określonego obszaru należy wykonać następujące czynności:

  1. Znaleziono dowody, że należy przyjrzeć się temu obszarowi.
  2. Sprawdzono inne obszary, o których wiesz, ale nie znaleziono niczego, co mogłoby wyjaśnić zauważony problem z wydajnością.

Aby uzyskać więcej informacji na temat podstaw pamięci i procesora CPU, zobacz blog Omówienie problemu przed próbą znalezienia rozwiązania.

Do zbierania danych dotyczących wydajności LOH można użyć następujących narzędzi:

Liczniki wydajności pamięci środowiska .NET CLR

Liczniki wydajności pamięci środowiska .NET CLR są zwykle dobrym pierwszym krokiem w badaniu problemów z wydajnością (chociaż zalecamy używanie zdarzeń ETW). Typowym sposobem na przyjrzenie się licznikom wydajności jest użycie monitora wydajności (perfmon.exe). Wybierz pozycję Dodaj (Ctrl + A), aby dodać interesujące liczniki dla procesów, które cię interesują. Dane licznika wydajności można zapisać w pliku dziennika.

Następujące dwa liczniki w kategorii pamięci środowiska CLR platformy .NET są istotne dla LOH:

  • # Kolekcje 2. generacji

    Przedstawia liczbę wywołań generacji 2 GCs od czasu rozpoczęcia procesu. Licznik jest inkrementowany na końcu kolekcji generacji 2 (zwanej także pełnym usuwaniem pamięci). Ten licznik wyświetla ostatnią zaobserwowaną wartość.

  • Duży rozmiar sterty obiektu

    Wyświetla bieżący rozmiar w bajtach, w tym wolnego miejsca, LOH. Ten licznik jest aktualizowany na końcu kolekcji śmieci, a nie na każdej alokacji.

Zrzut ekranu przedstawiający dodawanie liczników w monitorze wydajności.

Można również programowo wykonywać zapytania dotyczące liczników wydajności przy użyciu PerformanceCounter klasy . W przypadku LOH określ wartość ".NET CLR Memory" jako CategoryName i "Rozmiar sterty dużych obiektów" jako CounterName.

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

Console.WriteLine(performanceCounter.NextValue());

Często zbiera się liczniki programowo w ramach rutynowego procesu testowania. Gdy zauważysz liczniki z wartościami, które odbiegają od normy, użyj innych środków, aby uzyskać bardziej szczegółowe dane, które pomogą w dochodzeniu.

Uwaga / Notatka

Zalecamy używanie zdarzeń ETW zamiast liczników wydajności, ponieważ funkcja ETW udostępnia znacznie bogatsze informacje.

Zdarzenia ETW

Kolektor śmieci udostępnia bogaty zbiór zdarzeń ETW, aby pomóc zrozumieć, co dzieje się ze stertą i dlaczego. W poniższych wpisach w blogu pokazano, jak zbierać i interpretować zdarzenia GC za pomocą funkcji ETW:

Aby zidentyfikować nadmierne generowanie 2 GCs spowodowane tymczasowymi alokacjami LOH, należy zapoznać się z kolumną Powód wyzwolenia dla operacji GC. W przypadku prostego testu, który przydziela tylko tymczasowe duże obiekty, można zbierać informacje o zdarzeniach ETW za pomocą następującego polecenia PerfView :

perfview /GCCollectOnly /AcceptEULA /nogui collect

Wynik jest podobny do następującego:

Zrzut ekranu przedstawiający zdarzenia ETW w programie PerfView.

Jak widać, wszystkie GC są z generacji 2 i wszystkie są wyzwalane przez AllocLarge, co oznacza, że przydzielanie dużego obiektu wyzwoliło ten GC. Wiemy, że te alokacje są tymczasowe, ponieważ kolumna LOH Survival Rate % mówi 1%.

Możesz zebrać dodatkowe zdarzenia ETW, które pokazują, kto przyporządkował te duże obiekty. Następujący wiersz polecenia:

perfview /GCOnly /AcceptEULA /nogui collect

zbiera zdarzenie AllocationTick, które jest uruchamiane co około 100 tys. alokacji. Innymi słowy, zdarzenie jest wyzwalane za każdym razem, gdy jest przydzielany duży obiekt. Następnie możesz przyjrzeć się jednemu z widoków alokacji stosu pamięci GC (GC Heap Alloc), który pokazuje stosy wywołań alokujące duże obiekty.

Zrzut ekranu przedstawiający widok sterty odśmiecacza.

Jak widać, jest to bardzo prosty test, który po prostu przydziela duże obiekty z jego Main metody.

Debuger

Jeśli jedyne, co masz, to zrzut pamięci i musisz zbadać, jakie obiekty faktycznie znajdują się na LOH, możesz użyć rozszerzenia debugera SoS dostarczonego przez platformę .NET.

Uwaga / Notatka

Polecenia debugowania wymienione w tej sekcji mają zastosowanie do debugerów systemu Windows.

Poniżej przedstawiono przykładowe dane wyjściowe z analizy LOH:

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

Rozmiar sterty LOH wynosi (16 754 224 + 16 699 288 + 16 284 504) = 49 738 016 bajtów. Między adresami 023e1000 i 033db630, 8 008 736 bajtów zajmuje tablica System.Object obiektów, 6663 696 bajtów są zajęte przez tablicę System.Byte obiektów, a 2081 792 bajty są zajęte przez wolne miejsce.

Czasami debuger pokazuje, że całkowity rozmiar LOH jest mniejszy niż 85 000 bajtów. Dzieje się tak, ponieważ samo środowisko uruchomieniowe używa LOH do przydzielenia niektórych obiektów, które są mniejsze niż duży obiekt.

Ponieważ LOH nie jest kompaktowana, czasami uważa się, że LOH jest źródłem fragmentacji. Fragmentacja oznacza:

  • Fragmentacja zarządzanej sterty, która jest wskazywana przez wielkość wolnego miejsca między zarządzanymi obiektami. W systemie SoS polecenie !dumpheap –type Free wyświetla ilość wolnego miejsca między zarządzanymi obiektami.

  • Fragmentacja przestrzeni adresowej pamięci wirtualnej (VM), która jest pamięcią oznaczoną jako MEM_FREE. Możesz uzyskać to, korzystając z różnych poleceń debugera w windbg.

    W poniższym przykładzie pokazano fragmentację w przestrzeni maszyny wirtualnej:

    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)
    

Fragmentacja maszyny wirtualnej jest częściej widoczna, gdy tymczasowe duże obiekty wymagają, aby kolektor śmieci często pozyskiwał nowe segmenty sterty zarządzanej z systemu operacyjnego i zwalniał puste segmenty z powrotem do systemu operacyjnego.

Aby sprawdzić, czy LOH powoduje fragmentację maszyny wirtualnej, możesz ustawić punkt przerwania na funkcjach VirtualAlloc i VirtualFree, aby sprawdzić, kto je wywołał. Aby na przykład zobaczyć, kto próbował przydzielić fragmenty pamięci wirtualnej większe niż 8 MB z systemu operacyjnego, można ustawić punkt przerwania w następujący sposób:

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

To polecenie przerywa wykonanie i wchodzi do debugera, po czym pokazuje stos wywołań tylko wtedy, gdy funkcja VirtualAlloc jest wywoływana z rozmiarem alokacji większym niż 8 MB (0x800000).

CLR 2.0 dodał funkcję o nazwie Zarządzanie pamięcią VM, która może być przydatna w sytuacjach, w których segmenty (w tym te w dużych i małych stertach obiektów) są często pozyskiwane i zwalniane. Aby określić hoarding maszyn wirtualnych, należy ustawić flagę startową za pomocą STARTUP_HOARD_GC_VM poprzez interfejs API hostingu. Zamiast zwalniać puste segmenty z powrotem do systemu operacyjnego, CLR dezaktywuje pamięć w tych segmentach i umieszcza je na liście rezerwowej. (Należy pamiętać, że CLR nie robi tego w przypadku segmentów, które są zbyt duże). CLR później wykorzystuje te segmenty do zaspokojenia nowych żądań segmentów. Następnym razem, gdy aplikacja potrzebuje nowego segmentu, CLR używa jednego z tej listy rezerwowej, jeśli znajdzie taki, który jest wystarczająco duży.

Zarządzanie zasobami maszyn wirtualnych jest również przydatne w przypadku aplikacji, które chcą utrzymać już pozyskane segmenty, takich jak dominujące w systemie aplikacje serwerowe, aby uniknąć błędów z powodu braku pamięci.

Zdecydowanie zalecamy dokładne przetestowanie aplikacji podczas korzystania z tej funkcji, aby upewnić się, że aplikacja ma dość stabilne użycie pamięci.