Udostępnij za pośrednictwem


Zbieranie śmieci i wydajność

W tym artykule opisano problemy związane z odzyskiwaniem pamięci i użyciem pamięci. Rozwiązuje problemy związane z zarządzaną stertą i wyjaśnia, jak zminimalizować wpływ zbierania śmieci na aplikacje. Każdy problem zawiera linki do procedur, których można użyć do zbadania problemów.

Narzędzia do analizy wydajności

W poniższych sekcjach opisano narzędzia dostępne do badania problemów z użyciem pamięci i zbieraniem śmieci. Procedury przedstawione w dalszej części tego artykułu dotyczą tych narzędzi.

Liczniki wydajności pamięci

Liczniki wydajności umożliwiają zbieranie danych dotyczących wydajności. Aby uzyskać instrukcje, zobacz Profilowanie środowiska uruchomieniowego. Kategoria liczników wydajności pamięci .NET CLR, zgodnie z opisem w temacie Liczniki wydajności na platformie .NET, zawiera informacje na temat zbieracza pamięci.

Debugowanie za pomocą SOS

Do inspekcji obiektów na zarządzanym stercie możesz użyć debugera systemu Windows (WinDbg).

Aby zainstalować usługę WinDbg, zainstaluj narzędzia debugowania dla systemu Windows na stronie Pobieranie narzędzi debugowania dla systemu Windows .

Zdarzenia ETW odzyskiwania pamięci

Śledzenie zdarzeń dla systemu Windows (ETW) to system śledzenia, który uzupełnia obsługę profilowania i debugowania zapewnianą przez platformę .NET. Począwszy od programu .NET Framework 4, zdarzenia ETW zbierania śmieci przechwytują przydatne informacje dotyczące analizowania zarządzanej sterty pod względem statystycznym. Na przykład zdarzenie GCStart_V1, które jest zgłaszane, gdy ma nastąpić kolekcja śmieci, zawiera następujące informacje:

  • Która generacja obiektów jest kolekcjonowana.
  • Co wyzwoliło odśmiecanie pamięci.
  • Typ zbierania śmieci (współbieżne lub niewspółbieżne).

Rejestrowanie zdarzeń ETW jest wydajne i nie będzie maskować żadnych problemów z wydajnością związanych z odzyskiwaniem pamięci. Proces może udostępniać własne zdarzenia obok zdarzeń ETW. Po zalogowaniu zarówno zdarzenia aplikacji, jak i zdarzenia kolekcji śmieci mogą być skorelowane, aby określić, jak i kiedy występują problemy ze stertą. Na przykład aplikacja serwera może dostarczać zdarzenia na początku i na końcu żądania klienta.

Interfejs API profilowania

Interfejsy profilowania środowiska uruchomieniowego języka wspólnego (CLR) zawierają szczegółowe informacje o obiektach, które zostały zmienione podczas procesu odśmiecania pamięci. Profilera można powiadomić, kiedy rozpoczyna się i kończy zbieranie śmieci. Może dostarczać raporty o obiektach na zarządzanym stercie, w tym identyfikację obiektów w każdej generacji. Aby uzyskać więcej informacji, zobacz Profilowanie — omówienie.

Profilerzy mogą dostarczać kompleksowe informacje. Jednak złożone profileery mogą potencjalnie modyfikować zachowanie aplikacji.

Monitorowanie zasobów domeny aplikacji

Począwszy od programu .NET Framework 4, monitorowanie zasobów domeny aplikacji (ARM) umożliwia hostom monitorowanie użycia procesora i pamięci przez domenę aplikacji. Aby uzyskać więcej informacji, zobacz Application Domain Resource Monitoring (Monitorowanie zasobów domeny aplikacji).

Rozwiązywanie problemów z wydajnością

Pierwszym krokiem jest ustalenie, czy problem jest rzeczywiście wyrzucanie elementów bezużytecznych. Jeśli okaże się, że tak jest, wybierz z poniższej listy, aby rozwiązać problem.

Problem: Występuje wyjątek związany z brakiem pamięci

Istnieją dwa uzasadnione przypadki, w których można wyrzucić kontrolowany OutOfMemoryException.

  • Zabraknie pamięci wirtualnej.

    Kolektor śmieci przydziela pamięć z systemu w segmentach o wstępnie określonym rozmiarze. Jeśli alokacja wymaga dodatkowego segmentu, ale w przestrzeni pamięci wirtualnej procesu nie ma sąsiadującego wolnego bloku, alokacja zarządzanej sterty nie powiedzie się.

  • Brak wystarczającej ilości pamięci fizycznej do przydzielenia.

Testy wydajności
Ustal, czy wyjątek braku pamięci jest zarządzany.
Określ, ile pamięci wirtualnej można zarezerwować.
Ustal, czy jest wystarczająca ilość pamięci fizycznej.

Jeśli ustalisz, że wyjątek nie jest uzasadniony, skontaktuj się z działem obsługi klienta i wsparciem technicznym firmy Microsoft, aby uzyskać następujące informacje.

  • Stos z wyjątkiem braku pamięci w zarządzanym środowisku.
  • Pełny zrzut pamięci.
  • Dane, które dowodzą, że nie jest to uzasadniony wyjątek braku pamięci, w tym dane pokazujące, że pamięć wirtualna lub fizyczna nie jest problemem.

Problem: Proces używa zbyt dużej ilości pamięci

Typowym założeniem jest to, że użycie pamięci wyświetlane na karcie Wydajność Menedżera zadań systemu Windows może wskazywać, kiedy jest używana zbyt duża ilość pamięci. Jednak ten ekran odnosi się do zestawu roboczego; nie udostępnia informacji o użyciu pamięci wirtualnej.

Jeśli ustalisz, że problem jest spowodowany przez zarządzaną stertę, musisz zmierzyć zarządzaną stertę w czasie, aby określić wszelkie wzorce.

Jeśli ustalisz, że problem nie jest spowodowany przez zarządzaną stertę, musisz użyć debugowania natywnego.

Testy wydajności
Określ, ile pamięci wirtualnej można zarezerwować.
Określ ilość pamięci zatwierdzanej przez zarządzaną stertę.
Określ, ile pamięci rezerwuje zarządzana sterta.
Zidentyfikuj duże obiekty w pokoleniu 2.
Wyznacz referencje do obiektów.

Problem: Moduł odśmiecający nie odzyskuje obiektów wystarczająco szybko

Gdy wygląda na to, że obiekty nie są usuwane zgodnie z oczekiwaniami podczas zbierania śmieci, należy określić, czy istnieją silne odwołania do tych obiektów.

Ten problem może również wystąpić, jeśli nie było zebrania śmieci dla generacji, która zawiera martwy obiekt, co oznacza, że finalizator martwego obiektu nie został uruchomiony. Na przykład, jest to możliwe w przypadku uruchamiania aplikacji w jednowątkowym apartamencie (STA), gdy wątek obsługujący kolejkę finalizatora nie może do niej wywołać.

Testy wydajności
Sprawdź odwołania do obiektów.
Ustal, czy został uruchomiony finalizator.
Ustal, czy istnieją obiekty oczekujące na sfinalizowanie.

Problem: Zarządzany stos jest zbyt fragmentaryzowany

Poziom fragmentacji jest obliczany jako stosunek wolnego miejsca do całkowitej przydzielonej pamięci dla generacji. W przypadku generacji 2 akceptowalny poziom fragmentacji wynosi nie więcej niż 20%. Ponieważ generacja 2 może być bardzo duża, stosunek fragmentacji jest ważniejszy niż wartość bezwzględna.

Brak wolnego miejsca w generacji 0 nie jest problemem, ponieważ jest to generacja, w której są przydzielane nowe obiekty.

Fragmentacja zawsze występuje w dużej stercie obiektów, ponieważ nie jest kompresowana. Wolne sąsiadujące obiekty są naturalnie scalane w jedną przestrzeń, aby zaspokoić duże żądania alokacji obiektów.

Fragmentacja może stać się problemem w generacji 1 i 2. Jeśli te generacje mają dużą ilość wolnego miejsca po zbieraniu śmieci, użycie obiektów w aplikacji może wymagać modyfikacji, a także należy rozważyć ponowną ocenę okresu istnienia obiektów długoterminowych.

Nadmierne przypinanie obiektów może zwiększyć fragmentację. Jeśli fragmentacja jest duża, zbyt wiele obiektów mogło zostać przypiętych.

Jeśli fragmentacja pamięci wirtualnej uniemożliwia modułowi odśmiecania pamięci dodawanie segmentów, przyczyny mogą być następujące:

  • Częste ładowanie i rozładowywanie wielu małych zestawów.

  • Przechowywanie zbyt wielu odwołań do obiektów COM podczas współpracy z kodem niezarządzanym.

  • Tworzenie dużych obiektów przejściowych, co powoduje, że duże sterty obiektów często przydzielają i zwalniają segmenty sterty.

    Podczas hostowania środowiska CLR aplikacja może zażądać, aby moduł odśmiecacz pamięci zachował swoje segmenty. Zmniejsza to częstotliwość alokacji segmentów. Jest to realizowane przy użyciu flagi STARTUP_HOARD_GC_VM w Wyliczeniu STARTUP_FLAGS.

Testy wydajności
Określ ilość wolnego miejsca na zarządzanym stercie.
Określ liczbę przypiętych obiektów.

Jeśli uważasz, że nie ma uzasadnionej przyczyny fragmentacji, skontaktuj się z działem obsługi klienta i pomocy technicznej firmy Microsoft.

Problem: Przerwy w gospodarowaniu pamięcią są zbyt długie

Zbieranie śmieci działa w czasie rzeczywistym o charakterze miękkim, więc aplikacja musi być w stanie tolerować pewne przerwy. Kryterium miękkiego czasu rzeczywistego jest to, że 95% operacji musi zakończyć się na czas.

W przypadku współbieżnego odzyskiwania pamięci zarządzane wątki mogą działać podczas zbierania, co oznacza, że pauzy są bardzo krótkie.

Tymczasowe odśmiecanie pamięci (generacje 0 i 1) trwa tylko kilka milisekund, więc skrócenie przerw zazwyczaj nie jest możliwe. Można jednak zmniejszyć liczbę wstrzymań w kolekcjach generacji 2, zmieniając wzorzec żądań alokacji przez aplikację.

Kolejną, dokładniejszą metodą jest użycie zdarzeń ETW związanych z oczyszczaniem pamięci. Chronometraż kolekcji można znaleźć, dodając różnice sygnatur czasowych dla sekwencji zdarzeń. Cała sekwencja zbierania obejmuje zawieszenie silnika wykonawczego, sam proces zbierania śmieci i wznowienie działania silnika wykonawczego.

Możesz użyć powiadomień Garbage Collection, aby określić, czy serwer zbliża się do kolekcji generacji 2 i czy przekierowanie żądań do innego serwera może złagodzić problemy z przerwami.

Testy wydajności
Określ czas trwania procesów oczyszczania pamięci.
Ustal, co spowodowało czyszczenie pamięci.

Problem: Generacja 0 jest zbyt duża

Generacja 0 może mieć większą liczbę obiektów w systemie 64-bitowym, zwłaszcza w przypadku używania odzyskiwania pamięci serwera zamiast odzyskiwania pamięci stacji roboczej. Wynika to z faktu, że próg wyzwalania zbierania śmieci generacji 0 jest wyższy w tych środowiskach wykonywania, ponadto kolekcje generacji 0 mogą być znacznie większe. Wydajność jest poprawiana, gdy aplikacja przydziela więcej pamięci przed uruchomieniem zarządzania pamięcią.

Problem: Zużycie CPU podczas kolekcji śmieci jest za wysokie

Użycie procesora będzie wysokie podczas kolekcji śmieci. Jeśli znaczna ilość czasu procesu jest poświęcana na odzyskiwanie pamięci, liczba kolekcji jest zbyt duża lub kolekcja trwa zbyt długo. Zwiększona szybkość alokacji obiektów na zarządzanym stercie powoduje częstsze odśmiecanie pamięci. Zmniejszenie szybkości alokacji zmniejsza częstotliwość czyszczenia pamięci.

Współczynniki alokacji można monitorować przy użyciu licznika Allocated Bytes/second wydajności. Aby uzyskać więcej informacji, zobacz Liczniki wydajności na platformie .NET.

Czas trwania kolekcji jest przede wszystkim czynnikiem liczby obiektów, które przetrwają po alokacji. Kolektor śmieci musi przechodzić przez dużą ilość pamięci, jeśli wiele obiektów pozostaje do zebrania. Praca kompaktowania ocalałych jest czasochłonna. Aby określić, ile obiektów zostało obsłużonych podczas zbierania śmieci, ustaw punkt przerwania w debugerze na końcowym etapie dla określonej generacji.

Testy wydajności
Ustal, czy wysokie użycie CPU jest spowodowane przez kolektor śmieci.
Ustaw punkt przerwania na końcu zbierania śmieci.

Wskazówki dotyczące rozwiązywania problemów

W tej sekcji opisano wytyczne, które należy wziąć pod uwagę podczas rozpoczynania badań.

Odzyskiwanie pamięci stacji roboczej lub serwera

Ustal, czy używasz poprawnego typu odzyskiwania pamięci. Jeśli Twoja aplikacja używa wielu wątków i instancji obiektów, użyj kolekcji śmieci serwera zamiast kolekcji śmieci stacji roboczej. Zarządzanie pamięcią serwera jest wielowątkowe, podczas gdy zarządzanie pamięcią stacji roboczej wymaga wielu wystąpień aplikacji do uruchamiania własnych wątków zarządzania pamięcią i konkurowania o czas CPU.

Aplikacja, która ma niskie obciążenie i wykonuje zadania rzadko w tle, takie jak usługa, może używać odśmiecania pamięci na stacji roboczej z wyłączonym działaniem współbieżnym.

Kiedy należy zmierzyć rozmiar zarządzanej sterty

Jeśli nie używasz profilera, musisz ustanowić spójny wzorzec pomiaru w celu efektywnego diagnozowania problemów z wydajnością. Rozważ następujące kwestie, aby ustalić harmonogram:

  • W przypadku pomiaru po przeprowadzeniu oczyszczania pamięci generacji 2 cała zarządzana sterta będzie wolna od zbędnych (martwych) obiektów.
  • Jeśli dokonasz pomiaru bezpośrednio po odśmieceniu pamięci w generacji 0, obiekty z generacji 1 i 2 jeszcze nie zostaną sprzątnięte.
  • Jeśli mierzysz bezpośrednio przed procesem odzyskiwania pamięci, zmierzysz jak najwięcej alokacji zanim rozpocznie się odzyskiwanie pamięci.
  • Pomiar podczas zbierania śmieci jest problematyczny, ponieważ struktury danych zarządcy pamięci nie są w prawidłowym stanie do przechodzenia i mogą nie być w stanie zapewnić pełnych wyników. Jest to celowe.
  • Gdy używasz zbierania pamięci stacji roboczej z równoczesnym zbieraniem pamięci, odzyskane obiekty nie są zagęszczane, więc rozmiar sterty może być taki sam lub większy (fragmentacja może sprawiać, że wydaje się większa).
  • Współbieżne odzyskiwanie pamięci w generacji 2 jest opóźnione, gdy obciążenie pamięci fizycznej jest zbyt wysokie.

W poniższej procedurze opisano sposób ustawiania punktu przerwania, aby można było zmierzyć zarządzaną stertę.

Aby ustawić punkt przerwania na końcu zbierania śmieci

  • W systemie WinDbg z załadowanym rozszerzeniem debugera SOS wprowadź następujące polecenie:

    bp mscorwks!WKS::GCHeap::RestartEE "j (dwo(mscorwks!WKS::GCHeap::GcCondemnedGeneration)==2) 'kb';'g'"

    Ustaw GcCondemnedGeneration wartość żądanej generacji. To polecenie wymaga symboli prywatnych.

    To polecenie wymusza przerwanie, jeśli RestartEE jest wykonywane po odzyskaniu obiektów generacji 2 dla zbierania śmieci.

    W przypadku serwerowego zbierania śmieci tylko jeden wątek wywołuje RestartEE, więc punkt przerwania wystąpi tylko raz podczas zbierania śmieci generacji 2.

Procedury sprawdzania wydajności

W tej sekcji opisano następujące procedury izolowania przyczyny problemu z wydajnością:

Aby ustalić, czy problem jest spowodowany przez proces odzyskiwania pamięci

  • Sprawdź następujące dwa liczniki wydajności pamięci:

    • % czas w GC. Przedstawia procent czasu, jaki upłynął na wykonanie odzyskiwania pamięci po ostatnim cyklu zbierania nieużytków. Użyj tego licznika, aby określić, czy kolektor śmieci poświęca zbyt dużo czasu na udostępnienie zarządzanej pamięci sterty. Jeśli czas spędzony na zbieraniu śmieci jest stosunkowo krótki, może to wskazywać na problem z zasobami poza zarządzaną stertą. Licznik ten może być niedokładny w przypadku współbieżnego lub zbierania śmieci w tle.

    • # Łączna liczba zatwierdzonych bajtów. Przedstawia ilość pamięci wirtualnej, która jest obecnie zatwierdzana przez moduł odśmiecający pamięć. Użyj tego licznika, aby określić, czy pamięć zużywana przez garbage collector jest nadmierną częścią pamięci używanej przez aplikację.

    Większość liczników wydajności pamięci jest aktualizowana po zakończeniu każdego zbierania śmieci. W związku z tym mogą one nie odzwierciedlać bieżących warunków, o których chcesz uzyskać informacje.

Aby określić, czy wyjątek braku pamięci jest obsługiwany

  1. W debugerze WinDbg lub Visual Studio z załadowanym rozszerzeniem debugera SOS wprowadź następujące polecenie: drukuj wyjątek (pe).

    !pe

    Jeśli wyjątek jest zarządzany, OutOfMemoryException jest wyświetlany jako typ wyjątku, jak pokazano w poniższym przykładzie.

    Exception object: 39594518
    Exception type: System.OutOfMemoryException
    Message: <none>
    InnerException: <none>
    StackTrace (generated):
    
  2. Jeśli dane wyjściowe nie określają wyjątku, musisz ustalić, z którego wątku pochodzi wyjątek braku pamięci. Wprowadź następujące polecenie w debugerze, aby wyświetlić wszystkie wątki ze swoimi stosami wywołań:

    ~\*kb

    Wątek ze stosem zawierającym wywołania wyjątków jest wskazany za pomocą argumentu RaiseTheException. Jest to obiekt wyjątku zarządzanego.

    28adfb44 7923918f 5b61f2b4 00000000 5b61f2b4 mscorwks!RaiseTheException+0xa0
    
  3. Aby zrzucić zagnieżdżone wyjątki, można użyć następującego polecenia.

    !pe -nested

    Jeśli nie znajdziesz żadnych wyjątków, wyjątek braku pamięci pochodzi z niezarządzanego kodu.

Aby określić, ile pamięci wirtualnej można zarezerwować

  • W systemie WinDbg z załadowanym rozszerzeniem debugera SOS wprowadź następujące polecenie, aby uzyskać największy bezpłatny region:

    !address -summary

    Największy wolny region jest wyświetlany, jak pokazano w poniższym wyniku.

    Largest free region: Base 54000000 - Size 0003A980
    

    W tym przykładzie rozmiar największego wolnego regionu wynosi około 24000 KB (3A980 w systemie szesnastkowym). Ten obszar jest znacznie mniejszy niż to, czego potrzebuje garbage collector dla segmentu.

    -lub-

  • Użyj polecenia vmstat:

    !vmstat

    Największa wolna przestrzeń jest największą wartością w kolumnie MAXIMUM, jak pokazano w poniższych wyjściowych danych.

    TYPE        MINIMUM   MAXIMUM     AVERAGE   BLK COUNT   TOTAL
    ~~~~        ~~~~~~~   ~~~~~~~     ~~~~~~~   ~~~~~~~~~~  ~~~~
    Free:
    Small       8K        64K         46K       36          1,671K
    Medium      80K       864K        349K      3           1,047K
    Large       1,384K    1,278,848K  151,834K  12          1,822,015K
    Summary     8K        1,278,848K  35,779K   51          1,824,735K
    

Aby ustalić, czy jest wystarczająca ilość pamięci fizycznej

  1. Uruchom Menedżera zadań systemu Windows.

  2. Na karcie Performance spójrz na zarezerwowaną wartość. (W systemie Windows 7 spójrz na Commit (KB) w System group.)

    Jeśli wartość Total znajduje się blisko Limit, brakuje pamięci fizycznej.

Aby określić ilość pamięci zatwierdzanej przez zarządzaną stertę

  • Użyj licznika # Total committed bytes wydajności pamięci, aby uzyskać liczbę bajtów, które są zatwierdzane przez stertę zarządzaną. Automatyczny zarządca pamięci alokuje kawałki w segmencie zgodnie z potrzebami, a nie wszystkim na raz.

    Uwaga / Notatka

    Nie używaj # Bytes in all Heaps jako miernika wydajności, ponieważ nie odzwierciedla rzeczywistego wykorzystania pamięci przez stertę zarządzaną. Rozmiar generacji jest uwzględniany w tej wartości i stanowi jej rozmiar progowy, to znaczy rozmiar, który powoduje zbieranie śmieci, jeśli generacja jest wypełniona obiektami. W związku z tym ta wartość zwykle wynosi zero.

Aby określić, ile pamięci jest rezerwowane przez zarządzaną stertę

  • Użyj licznika # Total reserved bytes wydajności pamięci.

    Moduł odśmiecania pamięci rezerwuje pamięć w segmentach i można określić, gdzie zaczyna się segment, używając polecenia eeheap.

    Ważne

    Chociaż można określić ilość pamięci przydzielanej przez mechanizm zbierania śmieci dla każdego segmentu, rozmiar segmentu jest zależny od implementacji i może ulec zmianie w okresowych aktualizacjach, w dowolnym momencie. Aplikacja nigdy nie powinna zakładać ani nie zależeć od określonego rozmiaru segmentu, ani nie powinna podejmować próby skonfigurowania ilości pamięci dostępnej dla alokacji segmentów.

  • W debugerze WinDbg lub Visual Studio z załadowanym rozszerzeniem debugera SOS wprowadź następujące polecenie:

    !eeheap -gc

    Wynik jest następujący.

    Number of GC Heaps: 2
    ------------------------------
    Heap 0 (002db550)
    generation 0 starts at 0x02abe29c
    generation 1 starts at 0x02abdd08
    generation 2 starts at 0x02ab0038
    ephemeral segment allocation context: none
      segment    begin allocated     size
    02ab0000 02ab0038  02aceff4 0x0001efbc(126908)
    Large object heap starts at 0x0aab0038
      segment    begin allocated     size
    0aab0000 0aab0038  0aab2278 0x00002240(8768)
    Heap Size   0x211fc(135676)
    ------------------------------
    Heap 1 (002dc958)
    generation 0 starts at 0x06ab1bd8
    generation 1 starts at 0x06ab1bcc
    generation 2 starts at 0x06ab0038
    ephemeral segment allocation context: none
      segment    begin allocated     size
    06ab0000 06ab0038  06ab3be4 0x00003bac(15276)
    Large object heap starts at 0x0cab0038
      segment    begin allocated     size
    0cab0000 0cab0038  0cab0048 0x00000010(16)
    Heap Size    0x3bbc(15292)
    ------------------------------
    GC Heap Size   0x24db8(150968)
    

    Adresy wskazywane przez "segment" to adresy początkowe segmentów.

Aby określić duże obiekty w generacji 2

  • W debugerze WinDbg lub Visual Studio z załadowanym rozszerzeniem debugera SOS wprowadź następujące polecenie:

    !dumpheap –stat

    Jeśli zarządzana sterta jest duża, może upłynąć trochę czasu, zanim dumpheap zakończy działanie.

    Możesz rozpocząć analizowanie z kilku ostatnich wierszy danych wyjściowych, ponieważ wyświetlają one listę obiektów, które używają największej ilości miejsca. Przykład:

    2c6108d4   173712     14591808 DevExpress.XtraGrid.Views.Grid.ViewInfo.GridCellInfo
    00155f80      533     15216804      Free
    7a747c78   791070     15821400 System.Collections.Specialized.ListDictionary+DictionaryNode
    7a747bac   700930     19626040 System.Collections.Specialized.ListDictionary
    2c64e36c    78644     20762016 DevExpress.XtraEditors.ViewInfo.TextEditViewInfo
    79124228   121143     29064120 System.Object[]
    035f0ee4    81626     35588936 Toolkit.TlkOrder
    00fcae40     6193     44911636 WaveBasedStrategy.Tick_Snap[]
    791242ec    40182     90664128 System.Collections.Hashtable+bucket[]
    790fa3e0  3154024    137881448 System.String
    Total 8454945 objects
    

    Ostatni obiekt wymieniony jest ciągiem i zajmuje najwięcej miejsca. Możesz zbadać aplikację, aby zobaczyć, jak można zoptymalizować obiekty ciągów. Aby wyświetlić ciągi z zakresu od 150 do 200 bajtów, wprowadź następujące wartości:

    !dumpheap -type System.String -min 150 -max 200

    Przykład wyników wygląda następująco.

    Address  MT           Size  Gen
    1875d2c0 790fa3e0      152    2 System.String HighlightNullStyle_Blotter_PendingOrder-11_Blotter_PendingOrder-11
    …
    

    Użycie liczby całkowitej zamiast ciągu dla identyfikatora może być bardziej wydajne. Jeśli ten sam ciąg jest powtarzany tysiące razy, rozważ internowanie ciągu. Aby uzyskać więcej informacji na temat internowania ciągów, zobacz temat referencyjny dla metody String.Intern.

Aby określić odwołania do obiektów

  • W systemie WinDbg z załadowanym rozszerzeniem debugera SOS wprowadź następujące polecenie, aby wyświetlić listę odwołań do obiektów:

    !gcroot

    -lub-

  • Aby określić odwołania do określonego obiektu, dołącz adres:

    !gcroot 1c37b2ac

    Korzenie znalezione na stosach mogą być fałszywie dodatnie. Aby uzyskać więcej informacji, użyj polecenia !help gcroot.

    ebx:Root:19011c5c(System.Windows.Forms.Application+ThreadContext)->
    19010b78(DemoApp.FormDemoApp)->
    19011158(System.Windows.Forms.PropertyStore)->
    … [omitted]
    1c3745ec(System.Data.DataTable)->
    1c3747a8(System.Data.DataColumnCollection)->
    1c3747f8(System.Collections.Hashtable)->
    1c376590(System.Collections.Hashtable+bucket[])->
    1c376c98(System.Data.DataColumn)->
    1c37b270(System.Data.Common.DoubleStorage)->
    1c37b2ac(System.Double[])
    Scan Thread 0 OSTHread 99c
    Scan Thread 6 OSTHread 484
    

    Ukończenie gcroot polecenia może zająć dużo czasu. Każdy obiekt, który nie jest odzyskiwany przez zbieranie śmieci, jest żywym obiektem. Oznacza to, że jakiś element główny trzyma obiekt bezpośrednio lub pośrednio, więc gcroot ma zwrócić informacje o ścieżce do obiektu. Należy zbadać zwrócone grafy i sprawdzić, dlaczego te obiekty są nadal przywoływane.

Aby ustalić, czy został uruchomiony finalizator

  • Uruchom program testowy zawierający następujący kod:

    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    

    Jeśli test rozwiąże ten problem, oznacza to, że moduł odśmiecania pamięci nie odzyskuje obiektów, ponieważ finalizatory dla tych obiektów zostały zawieszone. Metoda GC.WaitForPendingFinalizers umożliwia finalizatorom wykonywanie zadań i rozwiązywanie problemu.

Aby ustalić, czy istnieją obiekty oczekujące na sfinalizowanie

  1. W debugerze WinDbg lub Visual Studio z załadowanym rozszerzeniem debugera SOS wprowadź następujące polecenie:

    !finalizequeue

    Przyjrzyj się liczbie obiektów gotowych do finalizacji. Jeśli liczba jest wysoka, musisz sprawdzić, dlaczego te finalizatory nie mogą w ogóle postępować lub nie mogą postępować wystarczająco szybko.

  2. Aby uzyskać dane wyjściowe wątków, wprowadź następujące polecenie:

    !threads -special

    To polecenie udostępnia dane wyjściowe, takie jak następujące.

       OSID     Special thread type
    2    cd0    DbgHelper
    3    c18    Finalizer
    4    df0    GC SuspendEE
    

    Wątek finalizatora wskazuje, który finalizator, jeśli istnieje, jest obecnie uruchamiany. Gdy wątek finalizatora nie uruchamia żadnych finalizatorów, czeka na zdarzenie, które nakaże mu rozpocząć pracę. Przez większość czasu zauważysz, że wątek finalizatora znajduje się w tym stanie, ponieważ działa z priorytetem THREAD_HIGHEST_PRIORITY i ma bardzo szybko zakończyć wykonywanie finalizatorów, jeśli takie istnieją.

Aby ustalić ilość wolnego miejsca w zarządzanej stercie

  • W debugerze WinDbg lub Visual Studio z załadowanym rozszerzeniem debugera SOS wprowadź następujące polecenie:

    !dumpheap -type Free -stat

    To polecenie wyświetla całkowity rozmiar wszystkich bezpłatnych obiektów na zarządzanym stercie, jak pokazano w poniższym przykładzie.

    total 230 objects
    Statistics:
          MT    Count    TotalSize Class Name
    00152b18      230     40958584      Free
    Total 230 objects
    
  • Aby określić wolne miejsce w generacji 0, wprowadź następujące polecenie dla informacji o zużyciu pamięci według generacji:

    !eeheap -gc

    To polecenie wyświetla dane wyjściowe podobne do poniższych. Ostatnia linia pokazuje segment przejściowy.

    Heap 0 (0015ad08)
    generation 0 starts at 0x49521f8c
    generation 1 starts at 0x494d7f64
    generation 2 starts at 0x007f0038
    ephemeral segment allocation context: none
    segment  begin     allocated  size
    00178250 7a80d84c  7a82f1cc   0x00021980(137600)
    00161918 78c50e40  78c7056c   0x0001f72c(128812)
    007f0000 007f0038  047eed28   0x03ffecf0(67103984)
    3a120000 3a120038  3a3e84f8   0x002c84c0(2917568)
    46120000 46120038  49e05d04   0x03ce5ccc(63855820)
    
  • Oblicz miejsce używane przez generację 0:

    ? 49e05d04-0x49521f8c

    Wynik jest następujący. Generacja 0 wynosi około 9 MB.

    Evaluate expression: 9321848 = 008e3d78
    
  • Następujące polecenie zrzutuje wolne miejsce w zakresie generacji 0:

    !dumpheap -type Free -stat 0x49521f8c 49e05d04

    Wynik jest następujący.

    ------------------------------
    Heap 0
    total 409 objects
    ------------------------------
    Heap 1
    total 0 objects
    ------------------------------
    Heap 2
    total 0 objects
    ------------------------------
    Heap 3
    total 0 objects
    ------------------------------
    total 409 objects
    Statistics:
          MT    Count TotalSize Class Name
    0015a498      409   7296540      Free
    Total 409 objects
    

    Te dane wyjściowe pokazują, że segment generacji 0 na stercie używa 9 MB miejsca dla obiektów i ma 7 MB wolne. Ta analiza pokazuje, w jakim stopniu generacja 0 przyczynia się do fragmentacji. Ta ilość użycia pamięci sterty powinna być odjęta od całkowitej ilości jako przyczyna fragmentacji spowodowanej przez obiekty długoterminowe.

Aby określić liczbę przypiętych obiektów

  • W debugerze WinDbg lub Visual Studio z załadowanym rozszerzeniem debugera SOS wprowadź następujące polecenie:

    !gchandles

    Wyświetlane statystyki obejmują liczbę przypiętych uchwytów, jak pokazano w poniższym przykładzie.

    GC Handle Statistics:
    Strong Handles:      29
    Pinned Handles:      10
    

Aby określić długość czasu trwania procesu zbierania śmieci

  • % Time in GC Sprawdź licznik wydajności pamięci.

    Wartość jest obliczana przy użyciu czasu interwału próbki. Ponieważ liczniki są aktualizowane na końcu każdego odzyskiwania pamięci, bieżąca próbka będzie miała taką samą wartość jak poprzednia próbka, jeśli w interwale nie wystąpiły żadne kolekcje.

    Czas kolekcji jest uzyskiwany przez pomnożenie czasu interwału próbki z wartością procentową.

    W poniższych danych przedstawiono cztery interwały próbkowania w ciągu dwóch sekund w 8-sekundowym badaniu. Kolumny Gen0, Gen1 i Gen2 pokazują całkowitą liczbę kolekcji odpadów zakończonych na koniec interwału dla tej generacji.

    Interval    Gen0    Gen1    Gen2    % Time in GC
            1       9       3       1              10
            2      10       3       1               1
            3      11       3       1               3
            4      11       3       1               3
    

    Te informacje nie pokazują, kiedy miało miejsce zbieranie śmieci, ale można określić liczbę zbierania śmieci, które miały miejsce w określonym przedziale czasu. Zakładając najgorszy przypadek, dziesiąta generacja 0 zbierania śmieci zakończyła się na początku drugiego przedziału czasowego, a jedenasta generacja 0 zbierania śmieci zakończyła się na końcu trzeciego przedziału czasowego. Czas między końcem dziesiątej a końcem jedenastej kolekcji śmieci wynosi około 2 sekundy, a licznik wydajności pokazuje 3%, więc czas trwania jedenastej kolekcji śmieci generacji 0 wyniósł 60 ms (2 sekundy * 3%).

    W następnym przykładzie istnieje pięć interwałów.

    Interval    Gen0    Gen1    Gen2     % Time in GC
            1       9       3       1                3
            2      10       3       1                1
            3      11       4       1                1
            4      11       4       1                1
            5      11       4       2               20
    

    Odzyskiwanie pamięci drugiej generacji 2 rozpoczęło się w czwartym interwale i zakończyło się w piątym interwale. Zakładając najgorszy scenariusz, ostatnie zbieranie śmieci dotyczyło kolekcji generacji 0, która zakończyła się na początku trzeciego interwału, a zbieranie śmieci generacji 2 zakończyło się na końcu piątego interwału. Zatem czas między końcem zbierania śmieci generacji 0 a końcem zbierania śmieci generacji 2 wynosi 4 sekundy. Ponieważ licznik % Time in GC wynosi 20%, maksymalny czas, który mogło zająć odzyskiwanie pamięci generacji 2, wynosi (4 sekundy * 20% = 800 ms).

  • Alternatywnie można określić długość odzyskiwania pamięci przy użyciu zdarzeń ETW odzyskiwania pamięci i analizować informacje, aby określić czas trwania odzyskiwania pamięci.

    Na przykład następujące dane przedstawiają sekwencję zdarzeń, która wystąpiła podczas niekonkurencyjnego odzyskiwania pamięci.

    Timestamp    Event name
    513052        GCSuspendEEBegin_V1
    513078        GCSuspendEEEnd
    513090        GCStart_V1
    517890        GCEnd_V1
    517894        GCHeapStats
    517897        GCRestartEEBegin
    517918        GCRestartEEEnd
    

    Wstrzymanie zarządzanego wątku trwało 26 mikrosekund (GCSuspendEEEndGCSuspendEEBegin_V1).

    Rzeczywista kolekcja śmieci trwała 4,8 ms (GCEnd_V1GCStart_V1).

    Wznawianie zarządzanych wątków trwało 21 μs (GCRestartEEEndGCRestartEEBegin).

    Poniższy wynik przedstawia przykład zbierania odpadów w tle, obejmując pola: procesu, wątku i zdarzenia. (Nie wszystkie dane są wyświetlane).

    timestamp(us)    event name            process    thread    event field
    42504385        GCSuspendEEBegin_V1    Test.exe    4372             1
    42504648        GCSuspendEEEnd         Test.exe    4372
    42504816        GCStart_V1             Test.exe    4372        102019
    42504907        GCStart_V1             Test.exe    4372        102020
    42514170        GCEnd_V1               Test.exe    4372
    42514204        GCHeapStats            Test.exe    4372        102020
    42832052        GCRestartEEBegin       Test.exe    4372
    42832136        GCRestartEEEnd         Test.exe    4372
    63685394        GCSuspendEEBegin_V1    Test.exe    4744             6
    63686347        GCSuspendEEEnd         Test.exe    4744
    63784294        GCRestartEEBegin       Test.exe    4744
    63784407        GCRestartEEEnd         Test.exe    4744
    89931423        GCEnd_V1               Test.exe    4372        102019
    89931464        GCHeapStats            Test.exe    4372
    

    Zdarzenie GCStart_V1 w 42504816 wskazuje, że jest to odzyskiwanie pamięci w tle, ponieważ ostatnie pole to 1. Staje się to zbiórką odpadów numer 102019.

    Zdarzenie GCStart występuje, ponieważ istnieje potrzeba efemerycznego odzyskiwania pamięci przed rozpoczęciem odzyskiwania pamięci w tle. To staje się zbiórką odpadów nr 102020.

    Przy 42514170, zakończono kolekcję odpadów nr 102020. W tym momencie zarządzane wątki są uruchamiane ponownie. Zostało to ukończone w wątku 4372, który zainicjował odzyskiwanie pamięci w tle.

    W wątku 4744 następuje zawieszenie. Jest to jedyny moment, w którym odzyskiwanie pamięci w tle musi zawiesić zarządzane wątki. Ten czas trwania wynosi około 99 ms ((63784407-63685394)/1000).

    Zdarzenie GCEnd odzyskiwania pamięci w tle odbędzie się na 89931423. Oznacza to, że odzyskiwanie pamięci w tle trwało około 47 sekund ((89931423-42504816)/1000).

    Podczas działania zarządzanych wątków można zobaczyć dowolną liczbę efemerycznych procesów zbierania śmieci.

Aby określić, co wyzwoliło odzyskiwanie pamięci

  • W debugerze WinDbg lub Visual Studio z załadowanym rozszerzeniem debugera SOS wprowadź następujące polecenie, aby wyświetlić wszystkie wątki ze swoimi stosami wywołań:

    ~*Baza wiedzy

    To polecenie wyświetla dane wyjściowe podobne do poniższych.

    0012f3b0 79ff0bf8 mscorwks!WKS::GCHeap::GarbageCollect
    0012f454 30002894 mscorwks!GCInterface::CollectGeneration+0xa4
    0012f490 79fa22bd fragment_ni!request.Main(System.String[])+0x48
    

    Jeśli odzyskiwanie pamięci zostało wywołane przez powiadomienie o niskiej ilości pamięci z systemu operacyjnego, stos wywołań jest podobny, z tą różnicą, że wątek to wątek finalizujący. Wątek finalizatora otrzymuje asynchroniczne powiadomienie o niskiej ilości dostępnej pamięci i inicjuje odśmiecanie pamięci.

    Jeśli odśmiecanie pamięci zostało spowodowane przez alokację pamięci, stos wygląda następująco:

    0012f230 7a07c551 mscorwks!WKS::GCHeap::GarbageCollectGeneration
    0012f2b8 7a07cba8 mscorwks!WKS::gc_heap::try_allocate_more_space+0x1a1
    0012f2d4 7a07cefb mscorwks!WKS::gc_heap::allocate_more_space+0x18
    0012f2f4 7a02a51b mscorwks!WKS::GCHeap::Alloc+0x4b
    0012f310 7a02ae4c mscorwks!Alloc+0x60
    0012f364 7a030e46 mscorwks!FastAllocatePrimitiveArray+0xbd
    0012f424 300027f4 mscorwks!JIT_NewArr1+0x148
    000af70f 3000299f fragment_ni!request..ctor(Int32, Single)+0x20c
    0000002a 79fa22bd fragment_ni!request.Main(System.String[])+0x153
    

    Pomocnik just-in-time (JIT_New*) w końcu wywołuje GCHeap::GarbageCollectGeneration. Jeśli ustalisz, że odśmiecanie pamięci generacji 2 jest spowodowane przez alokacje, musisz ustalić, które obiekty są zbierane podczas odśmiecania pamięci generacji 2 i jak tego unikać. Oznacza to, że chcesz określić różnicę między rozpoczęciem a końcem odzyskiwania pamięci generacji 2 oraz obiektami, które spowodowały zbieranie pamięci generacji 2.

    Na przykład wprowadź następujące polecenie w debugerze, aby wyświetlić początek kolekcji generacji 2:

    !dumpheap –stat

    Przykładowe dane wyjściowe (skrócone, aby pokazać obiekty, które używają największej przestrzeni):

    79124228    31857      9862328 System.Object[]
    035f0384    25668     11601936 Toolkit.TlkPosition
    00155f80    21248     12256296      Free
    79103b6c   297003     13068132 System.Threading.ReaderWriterLock
    7a747ad4   708732     14174640 System.Collections.Specialized.HybridDictionary
    7a747c78   786498     15729960 System.Collections.Specialized.ListDictionary+DictionaryNode
    7a747bac   700298     19608344 System.Collections.Specialized.ListDictionary
    035f0ee4    89192     38887712 Toolkit.TlkOrder
    00fcae40     6193     44911636 WaveBasedStrategy.Tick_Snap[]
    7912c444    91616     71887080 System.Double[]
    791242ec    32451     82462728 System.Collections.Hashtable+bucket[]
    790fa3e0  2459154    112128436 System.String
    Total 6471774 objects
    

    Powtórz polecenie na końcu generacji 2:

    !dumpheap –stat

    Przykładowe dane wyjściowe (skrócone, aby pokazać obiekty, które używają największej przestrzeni):

    79124228    26648      9314256 System.Object[]
    035f0384    25668     11601936 Toolkit.TlkPosition
    79103b6c   296770     13057880 System.Threading.ReaderWriterLock
    7a747ad4   708730     14174600 System.Collections.Specialized.HybridDictionary
    7a747c78   786497     15729940 System.Collections.Specialized.ListDictionary+DictionaryNode
    7a747bac   700298     19608344 System.Collections.Specialized.ListDictionary
    00155f80    13806     34007212      Free
    035f0ee4    89187     38885532 Toolkit.TlkOrder
    00fcae40     6193     44911636 WaveBasedStrategy.Tick_Snap[]
    791242ec    32370     82359768 System.Collections.Hashtable+bucket[]
    790fa3e0  2440020    111341808 System.String
    Total 6417525 objects
    

    Obiekty double[] zniknęły z końca danych wyjściowych, co oznacza, że zostały zebrane. Te obiekty stanowią około 70 MB. Pozostałe obiekty nie zmieniły się zbytnio. W związku z tym te double[] obiekty były powodem, dla którego doszło do odśmiecania pamięci tej generacji 2. Następnym krokiem jest ustalenie, dlaczego obiekty double[] są tam i dlaczego przestały działać. Możesz zapytać dewelopera kodu, z którego pochodzą te obiekty, lub użyć gcroot polecenia .

Aby ustalić, czy wysokie użycie CPU wynika z oczyszczania pamięci

  • Skoreluj wartość licznika % Time in GC wydajności pamięci z czasem procesu.

    % Time in GC Jeśli wartość gwałtownie wzrasta w tym samym czasie co czas trwania procesu, odzyskiwanie pamięci powoduje wysokie użycie procesora. W przeciwnym razie zaprofiluj aplikację, aby znaleźć miejsce występowania wysokiego użycia.

Zobacz także