Odzyskiwanie pamięci 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 odzyskiwania pamięci 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 odzyskiwaniem pamięci. 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 pamięci środowiska .NET CLR liczników wydajności, zgodnie z opisem w temacie Liczniki wydajności na platformie .NET, zawiera informacje na temat modułu odśmieceń pamięci.

Debugowanie za pomocą SOS

Do inspekcji obiektów na zarządzanym stercie można 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 odzyskiwania pamięci przechwytują przydatne informacje dotyczące analizowania zarządzanego sterty z punktu widzenia statystycznego. Na przykład GCStart_V1 zdarzenie, które jest zgłaszane, gdy ma nastąpić odzyskiwanie pamięci, zawiera następujące informacje:

  • Które generowanie obiektów jest zbierane.
  • Co wyzwoliło odzyskiwanie pamięci.
  • Typ odzyskiwania pamięci (współbieżne lub nie współ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 w połączeniu ze zdarzeniami ETW. Po zarejestrowaniu zdarzenia aplikacji i zdarzenia odzyskiwania pamięci mogą być skorelowane, aby określić, jak i kiedy występują problemy z 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órych dotyczy problem podczas odzyskiwania pamięci. Profiler można otrzymywać powiadomienia po uruchomieniu i zakończeniu odzyskiwania pamięci. 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 domen 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: Zgłaszany jest wyjątek braku pamięci

Istnieją dwa uzasadnione przypadki, w których udało OutOfMemoryException się zgłosić:

  • Zabraknie pamięci wirtualnej.

    Moduł odśmieceń pamięci przydziela pamięć z systemu w segmentach wstępnie określonego rozmiaru. Jeśli alokacja wymaga dodatkowego segmentu, ale nie ma ciągłego wolnego bloku pozostawionego w pamięci wirtualnej procesu, alokacja zarządzanej sterty zakończy się niepowodzeniem.

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

Testy wydajności
Ustal, czy jest zarządzany wyjątek poza pamięcią.
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 pomocą techniczną i pomocą techniczną firmy Microsoft, aby uzyskać następujące informacje:

  • Stos z zarządzanym wyjątkiem braku pamięci.
  • 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 ilość pamięci rezerw zarządzanego sterta.
Określanie dużych obiektów w generacji 2.
Określanie odwołań do obiektów.

Problem: Moduł odśmieceń pamięci nie odzyskuje obiektów wystarczająco szybko

Gdy wygląda na to, że obiekty nie są odzyskiwane zgodnie z oczekiwaniami w przypadku odzyskiwania pamięci, 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 odzyskiwania pamięci dla generacji zawierającej obiekt martwy, co oznacza, że finalizator obiektu martwego nie został uruchomiony. Na przykład jest to możliwe, gdy uruchamiasz aplikację apartamentu jednowątkowego (STA) i wątku, który obsługuje kolejkę finalizatora, nie może wywołać do niej.

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

Problem: Zarządzana sterta jest zbyt rozdrobniona

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żym stercie obiektu, ponieważ nie jest kompaktowana. Wolne obiekty, które sąsiadują, są naturalnie zwinięte 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 usunięciu pamięci, użycie obiektu aplikacji może wymagać modyfikacji i należy rozważyć ponowne obliczenie okresu istnienia długoterminowych obiektów.

Nadmierne przypinanie obiektów może zwiększyć fragmentację. Jeśli fragmentacja jest wysoka, 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 zwalnianie wielu małych zestawów.

  • Przechowywanie zbyt wielu odwołań do obiektów COM podczas współdziałania z kodem niezarządzającym.

  • 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 wyliczenie 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: Wstrzymanie odzyskiwania pamięci jest zbyt długie

Odzyskiwanie pamięci działa w czasie miękkim w czasie rzeczywistym, więc aplikacja musi mieć możliwość tolerowania niektórych wstrzymania. 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ą być uruchamiane podczas zbierania, co oznacza, że wstrzymanie jest bardzo minimalne.

Efemeryczne odzyskiwanie pamięci (generacje 0 i 1) trwa tylko kilka milisekund, więc zmniejszenie wstrzymania zwykle 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 odzyskiwania pamięci. Chronometraż kolekcji można znaleźć, dodając różnice sygnatur czasowych dla sekwencji zdarzeń. Cała sekwencja zbierania obejmuje zawieszenie aparatu wykonawczego, samo odzyskiwanie pamięci i wznowienie aparatu wykonywania.

Możesz użyć powiadomień odzyskiwania pamięci, aby określić, czy serwer ma mieć kolekcję generacji 2 i czy przekierowanie żądań do innego serwera może złagodzić wszelkie problemy z wstrzymaniami.

Testy wydajności
Określ czas odzyskiwania pamięci.
Ustal, co spowodowało odzyskiwanie 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 odzyskiwania pamięci generacji 0 jest wyższy w tych środowiskach, a kolekcje generacji 0 mogą być znacznie większe. Wydajność jest poprawiana, gdy aplikacja przydziela więcej pamięci przed wyzwoleniem odzyskiwania pamięci.

Problem: Użycie procesora CPU podczas odzyskiwania pamięci jest zbyt wysokie

Użycie procesora CPU będzie wysokie podczas odzyskiwania pamięci. 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 odzyskiwanie pamięci. Zmniejszenie szybkości alokacji zmniejsza częstotliwość odzyskiwania 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. Moduł odśmiecenia pamięci musi przechodzić przez dużą ilość pamięci, jeśli wiele obiektów pozostanie do zebrania. Praca kompaktowania ocalałych jest czasochłonna. Aby określić, ile obiektów zostało obsłużonych podczas kolekcji, ustaw punkt przerwania w debugerze na końcu odzyskiwania pamięci dla określonej generacji.

Testy wydajności
Ustal, czy wysokie użycie procesora CPU jest spowodowane przez odzyskiwanie pamięci.
Ustaw punkt przerwania na końcu odzyskiwania pamięci.

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 aplikacja używa wielu wątków i wystąpień obiektów, użyj odzyskiwania pamięci serwera zamiast odzyskiwania pamięci stacji roboczej. Odzyskiwanie pamięci serwera działa na wielu wątkach, podczas gdy odzyskiwanie pamięci stacji roboczej wymaga wielu wystąpień aplikacji do uruchamiania własnych wątków odzyskiwania pamięci i konkurowania o czas procesora CPU.

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

Kiedy należy zmierzyć zarządzany rozmiar 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 usunięciu pamięci generacji 2 cała zarządzana sterta będzie wolna od pamięci (martwych obiektów).
  • W przypadku pomiaru bezpośrednio po usunięciu pamięci generacji 0 obiekty z generacji 1 i 2 nie zostaną jeszcze zebrane.
  • Jeśli mierzysz bezpośrednio przed odzyskiwaniem pamięci, przed rozpoczęciem odzyskiwania pamięci zmierzysz jak najwięcej alokacji.
  • Pomiar podczas odzyskiwania pamięci jest problematyczny, ponieważ struktury danych modułu odśmiecania pamięci nie są w prawidłowym stanie przechodzenia i mogą nie być w stanie zapewnić pełnych wyników. Jest to celowe.
  • Gdy używasz odzyskiwania pamięci stacji roboczej z równoczesnym odzyskiwaniem pamięci, odzyskane obiekty nie są kompaktowane, więc rozmiar sterty może być taki sam lub większy (fragmentacja może wydawać 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 odzyskiwania pamięci

  • 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 w celu odzyskiwania pamięci.

    W przypadku odzyskiwania pamięci serwera tylko jeden wątek wywołuje metodę RestartEE, więc punkt przerwania będzie występować tylko raz podczas odzyskiwania pamięci 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 odzyskiwanie pamięci

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

    • % czasu w GC. Przedstawia procent czasu, który upłynął, wykonujący odzyskiwanie pamięci po ostatnim cyklu odzyskiwania pamięci. Użyj tego licznika, aby określić, czy moduł odśmiecający elementy bezużyteczne poświęca zbyt dużo czasu, aby udostępnić zarządzane miejsce stert. Jeśli czas spędzony w wyrzucaniu pamięci jest stosunkowo niski, może to wskazywać na problem z zasobami poza zarządzaną stertą. Ten licznik może nie być dokładny w przypadku współbieżnego lub odzyskiwania pamięci 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 moduł odśmiecenia pamięci jest nadmierną częścią pamięci używanej przez aplikację.

    Większość liczników wydajności pamięci jest aktualizowana na końcu każdego odzyskiwania pamięci. 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 poza pamięcią jest zarządzany

  1. W debugerze WinDbg lub Visual Studio z załadowanym rozszerzeniem debugera SOS wprowadź polecenie wyjątku drukowania (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ślą wyjątku, należy określić, z którego wątku pochodzi wyjątek poza 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 wskazywany RaiseTheException przez argument . 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 bezpłatny region jest wyświetlany, jak pokazano w poniższych danych wyjściowych.

    Largest free region: Base 54000000 - Size 0003A980
    

    W tym przykładzie rozmiar największego wolnego regionu wynosi około 24000 KB (3A980 w szesnastkowym). Ten region jest znacznie mniejszy niż to, czego potrzebuje moduł odśmieceń pamięci dla segmentu.

    -Lub-

  • vmstat Użyj polecenia :

    !vmstat

    Największy bezpłatny region jest największą wartością w kolumnie MAXIMUM, jak pokazano w poniższych danych wyjściowych.

    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 przyjrzyj Performance się zatwierdzonej wartości. (W systemie Windows 7 przyjrzyj się Commit (KB) w pliku 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 zatwierdzanych przez zarządzaną stertę. Moduł odśmieceń pamięci zatwierdza fragmenty w segmencie zgodnie z potrzebami, a nie w tym samym czasie.

    Uwaga

    Nie używaj licznika # Bytes in all Heaps wydajności, ponieważ nie reprezentuje rzeczywistego użycia pamięci przez zarządzaną stertę. Rozmiar generacji jest uwzględniany w tej wartości i jest w rzeczywistości jego rozmiarem progowym, czyli rozmiarem wywołującym odzyskiwanie pamięci, jeśli generowanie jest wypełnione obiektami. W związku z tym ta wartość zwykle wynosi zero.

Aby określić ilość pamięci rezerw zarządzanego sterta

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

    Moduł odśmiecający pamięć rezerwuje pamięć w segmentach i można określić, gdzie zaczyna się segment przy użyciu eeheap polecenia .

    Ważne

    Chociaż można określić ilość pamięci przydzielanej przez moduł odśmiecenia pamięci dla każdego segmentu, rozmiar segmentu jest specyficzny dla implementacji i może ulec zmianie w dowolnym momencie, w tym w okresowych aktualizacjach. 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, dumpheap może upłynąć trochę czasu.

    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. Na 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ż interning ciągu. Aby uzyskać więcej informacji na temat interningu ciągów, zobacz temat referencyjny dla String.Intern metody .

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 odzyskiwane przez odzyskiwanie pamięci, jest obiektem dynamicznym. Oznacza to, że jakiś element główny jest bezpośrednio lub pośrednio trzymający obiekt, dlatego gcroot powinien zwrócić informacje o ścieżce do obiektu. Należy zbadać zwrócone grafy i zobaczyć, 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, aby poinformować go, aby wykonać swoją pracę. Przez większość czasu zobaczysz wątek finalizatora w tym stanie, ponieważ działa w THREAD_HIGHEST_PRIORITY i ma zakończyć uruchamianie finalizatorów, jeśli w ogóle, bardzo szybko.

Aby określić ilość wolnego miejsca w zarządzanym 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. Ostatni wiersz pokazuje segment efemeryczny.

    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 część generacji 0 sterta używa 9 MB miejsca dla obiektów i ma 7 MB wolnego miejsca. Ta analiza pokazuje, w jakim stopniu generacja 0 przyczynia się do fragmentacji. Ta ilość użycia sterty powinna być obniżona z całkowitej kwoty jako przyczyny fragmentacji 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 dojść, jak pokazano w poniższym przykładzie.

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

Aby określić czas odzyskiwania pamięci

  • % 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, Gen1i Gen2 pokazują łączną liczbę kolekcji pamięci zakończonych do końca 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 są wyświetlane, gdy doszło do odzyskiwania pamięci, ale można określić liczbę odzyskiwania pamięci, które wystąpiły w interwale czasu. Zakładając najgorszy przypadek, dziesiąta generacja 0 odzyskiwania pamięci zakończyła się na początku drugiego interwału, a jedenasta generacja 0 wyrzucania pamięci zakończyła się na końcu trzeciego interwału. Czas między końcem dziesiątego a końcem jedenastego odzyskiwania pamięci wynosi około 2 sekundy, a licznik wydajności pokazuje 3%, więc czas trwania jedenastej generacji 0 odzyskiwania pamięci wynosił (2 sekundy * 3% = 60 ms).

    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. Przy założeniu najgorszego przypadku ostatnie odzyskiwanie pamięci dotyczyło kolekcji generacji 0, która zakończyła się na początku trzeciego interwału, a odzyskiwanie pamięci generacji 2 zakończyło się na końcu piątego interwału. W związku z tym czas między końcem odzyskiwania pamięci generacji 0 a końcem odzyskiwania pamięci 2 generacji wynosi 4 sekundy. % Time in GC Ponieważ licznik wynosi 20%, maksymalny czas potrzebny na odzyskiwanie pamięci generacji 2 to (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 niebieżnego 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 26us (GCSuspendEEEndGCSuspendEEBegin_V1).

    Rzeczywiste odzyskiwanie pamięci trwało 4,8 ms (GCEnd_V1GCStart_V1).

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

    Poniższe dane wyjściowe zawierają przykład odzyskiwania pamięci w tle i obejmują 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 odzyskiwaniem pamięci nr 102019.

    Zdarzenie GCStart występuje, ponieważ istnieje potrzeba efemerycznego odzyskiwania pamięci przed rozpoczęciem odzyskiwania pamięci w tle. Staje się to odzyskiwaniem pamięci nr 102020.

    Po 42514170 kończy się odzyskiwanie pamięci nr 102020. W tym momencie zarządzane wątki są uruchamiane ponownie. Zostało to ukończone w wątku 4372, co spowodowało wyzwolenie odzyskiwania pamięci w tle.

    W wątku 4744 wystę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 jest w 89931423. Oznacza to, że odzyskiwanie pamięci w tle trwało około 47sekund ((89931423-42504816)/1000).

    Podczas uruchamiania zarządzanych wątków można zobaczyć dowolną liczbę efemerycznych odzyskiwania pamięci.

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ń:

    ~*Kb

    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 spowodowane przez powiadomienie o niskiej ilości pamięci z systemu operacyjnego, stos wywołań jest podobny, z tą różnicą, że wątek jest wątkiem finalizatora. Wątek finalizatora pobiera asynchroniczne powiadomienie o niskiej ilości pamięci i wywołuje odzyskiwanie pamięci.

    Jeśli odzyskiwanie pamięci zostało spowodowane przez alokację pamięci, stos pojawia się w następujący sposób:

    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 metodę GCHeap::GarbageCollectGeneration. Jeśli ustalisz, że odzyskiwanie pamięci 2 generacji jest spowodowane przez alokacje, należy określić, które obiekty są zbierane przez odzyskiwanie pamięci generacji 2 i jak je uniknąć. 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 odzyskiwania pamięci tej generacji 2. Następnym krokiem jest ustalenie, dlaczego double[] są tam obiekty i dlaczego zmarły. Możesz zapytać dewelopera kodu, z którego pochodzą te obiekty, lub użyć gcroot polecenia .

Aby ustalić, czy wysokie użycie procesora CPU jest spowodowane przez odzyskiwanie pamięci

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

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

Zobacz też