Sterta dużych obiektów w systemach Windows

Moduł odśmiecanie pamięci platformy .NET (GC) dzieli obiekty na małe i duże obiekty. 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 kończy się 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, środowisko uruchomieniowe przydziela je na stercie dużego obiektu.

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

Moduł odśmiecający śmieci jest modułem zbierającym generowanie. 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 i długotrwałych obszarów obiektów.

Nowo przydzielone obiekty tworzą nową generację obiektów i są niejawnie generacji kolekcji 0. Jednak jeśli są dużymi obiektami, przechodzą na dużą stertę 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 pokolenia zbierane są również wszystkie młodsze pokolenia. Na przykład gdy wystąpi generacja 1 GC, zbierane są obie generacje 1 i 0. A kiedy dzieje się generacja 2 GC, cała sterta jest zbierana. 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 stert GC. Fizycznie obiekty znajdują się w zarządzanych segmentach stert. Zarządzany segment stert to fragment pamięci rezerwowany przez GC z systemu operacyjnego przez wywołanie funkcji VirtualAlloc w imieniu kodu zarządzanego. Po załadowaniu clR GC przydziela dwa początkowe segmenty sterty: jeden dla małych obiektów (sterty małych obiektów lub SOH) i jeden dla dużych obiektów (sterty dużych 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 umieszczana na segmencie LOH. Segmenty są zatwierdzane (w mniejszych fragmentach), ponieważ coraz więcej obiektów jest na 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ą najstarsze pokolenie, są nadal uważane za w najstarszym pokoleniu. Innymi słowy, ocalałych z pokolenia 2 są obiekty generacji 2; i ocalałych z LOH są obiekty LOH (które są zbierane z gen2).

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 wyzwoleniu odzyskiwania pamięci funkcja GC śledzi je za pośrednictwem obiektów na żywo i kompakuje je. Ale ponieważ kompaktowanie jest kosztowne, GC zamiata LOH; sprawia, że bezpłatna lista z martwych obiektów, które można użyć ponownie później, aby zaspokoić duże żądania alokacji obiektów. Sąsiadujące martwe obiekty są tworzone w jednym wolnym obiekcie.

.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.

Figure 1: A gen 0 GC and a gen 1 GC
Rysunek 1. Generacja 0 i generacja 1 GC.

Rysunek 2 pokazuje, że po generacji 2 GC, który widział, że Obj1 i Obj2 są martwe, GC tworzy ciągłe wolne miejsce z pamięci, które kiedyś zajmowane przez Obj1 i Obj2, który następnie został użyty do spełnienia żądania alokacji dla Obj4. Miejsce po ostatnim obiekcie , Obj3na koniec segmentu może być również używane do zaspokojenia żądań alokacji.

Figure 2: After a gen 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śmiecanie pamięci zwalnia segmenty, które nie mają na nich obiektów na żywo z powrotem do systemu operacyjnego przez wywołanie funkcji VirtualFree. Miejsce po ostatnim obiekcie na żywo na końcu segmentu zostanie anulowane (z wyjątkiem segmentu efemerycznego, w którym mieszka gen0/gen1, gdzie moduł odśmiecania pamięci zachowuje część zatwierdzoną, ponieważ aplikacja zostanie przydzielona od razu). 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 na końcu segmentu należy użyć miejsca, które zostanie anulowane, aby spełnić żądania alokacji dużych obiektów, ponownie zatwierdzi pamięć. (Aby uzyskać wyjaśnienie zatwierdzenia/decommit, zobacz dokumentację dotyczącą VirtualAlloc.)

Figure 3: LOH after a gen 2 GC
Rysunek 3. LOH po pokoleniu 2 GC

Kiedy jest zbierany duży obiekt?

Ogólnie rzecz biorąc, GC występuje w jednym z następujących trzech warunków:

  • Alokacja przekracza próg 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. Przydzielenie małych lub dużych obiektów zużywa odpowiednio progi generacji 0 i LOH. Gdy moduł odśmiecanie pamięci przydziela do generacji 1 i 2, zużywa swoje progi. Te progi są dynamicznie dostrojone w miarę uruchamiania programu.

    Jest to typowy przypadek; większość kontrolerów domeny ma miejsce z powodu alokacji na zarządzanym stercie.

  • Wywoływana GC.Collect jest metoda .

    Jeśli metoda bez GC.Collect() parametrów jest wywoływana lub inne przeciążenie jest przekazywane GC.MaxGeneration jako argument, LOH jest zbierane wraz z resztą zarządzanego sterta.

  • System jest w niskiej sytuacji w pamięci.

    Dzieje się tak, gdy moduł odśmieceń pamięci odbiera powiadomienie o dużej ilości pamięci z systemu operacyjnego. Jeśli moduł odśmieceń pamięci uważa, że wykonanie generacji 2 GC będzie produktywne, wyzwala jeden.

Implikacje dotyczące wydajności LOH

Alokacje na dużych stertach 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 rozdaje, jest czyszczone. 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 kolekcji.

    Ponieważ LOH i generacja 2 są zbierane razem, jeśli próg jednego z nich zostanie przekroczony, zostanie wyzwolona kolekcja generacji 2. 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 przydzielasz i puszczasz naprawdę duże obiekty.

  • Elementy tablicy z typami referencyjnymi.

    Bardzo duże obiekty na LOH są zwykle tablicami (bardzo rzadko mają obiekt wystąpienia, który jest naprawdę duży). Jeśli elementy tablicy są bogate w odwołania, wiąże się z kosztem, który nie występuje, jeśli elementy nie są bogate w odwołania. 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łanie się do węzła po prawej i lewej stronie węzła według 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, moduł odśmiecający pamięci musi przejść przez co najmniej dwa odwołania na element. Alternatywną metodą jest przechowywanie indeksu po prawej i lewej stronie węzłów:

    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. Moduł odśmiecający elementy bezużyteczne nie musi przeglądać żadnych odwołań dla węzła po lewej i prawej stronie.

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 są używane ponownie, zamiast przydzielać tymczasowe.

Zbieranie danych dotyczących 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. Wyczerpano inne obszary, o których wiesz, bez znajdowania niczego, co mogłoby wyjaśnić 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 monitor 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 zwiększany na końcu kolekcji generacji 2 (nazywanej również pełnym odzyskiwaniem 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 odzyskiwania pamięci, a nie na każdej alokacji.

Screenshot that shows adding counters in Performance Monitor.

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 "Duży rozmiar sterty obiektu" 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 dotrzesz liczniki z wartościami, które nie są zwykłe, użyj innych środków, aby uzyskać bardziej szczegółowe dane, aby ułatwić badanie.

Uwaga

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

zdarzenia ETW

Moduł odśmieceń pamięci udostępnia bogaty zestaw zdarzeń ETW, aby ułatwić zrozumienie, co robi sterta i dlaczego. W poniższych wpisach w blogu pokazano, jak zbierać i interpretować zdarzenia GC za pomocą funkcji ETW:

Aby zidentyfikować nadmierne generowanie 2 GC spowodowane tymczasowymi alokacjami LOH, zapoznaj się z kolumną Przyczyna wyzwalacza dla kontrolerów domeny. 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:

Screenshot that shows ETW events in PerfView.

Jak widać, wszystkie kontrolery domeny są generacji 2 GCs 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 informują, kto przydzielił 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 GC Heap Alloc, które pokazują stosy wywołań, które przydzieliły duże obiekty:

Screenshot that shows a garbage collector heap view.

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

Debuger

Jeśli wszystko, co masz, to zrzut pamięci i musisz sprawdzić, jakie obiekty są rzeczywiście na LOH, możesz użyć rozszerzenia debugera SoS dostarczonego przez platformę .NET.

Uwaga

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 ilość 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żna go pobrać przy użyciu 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)
    

Częściej widać fragmentację maszyny wirtualnej spowodowane tymczasowymi dużymi obiektami, które wymagają częstego pozyskiwania nowych segmentów sterty zarządzanej z systemu operacyjnego i zwalniania pustych z powrotem do systemu operacyjnego.

Aby sprawdzić, czy element LOH powoduje fragmentację maszyny wirtualnej, możesz ustawić punkt przerwania na maszynach VirtualAlloc i VirtualFree , aby zobaczyć, kto je nazwał. 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 dzieli się na debuger i pokazuje stos wywołań tylko wtedy, gdy element VirtualAlloc jest wywoływany z rozmiarem alokacji większym niż 8 MB (0x800000).

CLR 2.0 dodał funkcję o nazwie Hoarding maszyny wirtualnej, która może być przydatna w scenariuszach, w których segmenty (w tym na dużych i małych stertach obiektów) są często pozyskiwane i zwalniane. Aby określić hoarding maszyny wirtualnej, należy określić flagę uruchamiania wywoływaną STARTUP_HOARD_GC_VM za pośrednictwem interfejsu API hostingu. Zamiast zwalniać puste segmenty z powrotem do systemu operacyjnego, CLR dekomektuje pamięć na 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 używa tych segmentów do spełnienia nowych żądań segmentów. Następnym razem, gdy aplikacja potrzebuje nowego segmentu, clR używa go z tej listy rezerwowej, jeśli może znaleźć taki, który jest wystarczająco duży.

Hoarding maszyn wirtualnych jest również przydatny w przypadku aplikacji, które chcą trzymać się segmentów, które zostały już pozyskane, takich jak niektóre aplikacje serwerowe, które są dominującymi aplikacjami uruchomionymi w systemie, aby uniknąć wyjątków poza 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.