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.
- Zgłaszany jest wyjątek braku pamięci
- Proces używa zbyt dużej ilości pamięci
- Moduł odśmiecający śmieci nie odzyskuje obiektów wystarczająco szybko
- Zarządzana sterta jest zbyt rozdrobniona
- Wstrzymanie odzyskiwania pamięci jest zbyt długie
- Generacja 0 jest zbyt duża
- Użycie procesora CPU podczas odzyskiwania pamięci jest zbyt wysokie
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.
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ą:
- Ustal, czy problem jest spowodowany przez odzyskiwanie pamię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.
- 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.
- Ustal, czy został uruchomiony finalizator.
- Ustal, czy istnieją obiekty oczekujące na sfinalizowanie.
- Określ ilość wolnego miejsca na zarządzanym stercie.
- Określ liczbę przypiętych obiektów.
- Określ czas odzyskiwania pamięci.
- Ustal, co wyzwoliło odzyskiwanie pamięci.
- Ustal, czy wysokie użycie procesora CPU jest spowodowane przez odzyskiwanie pamię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
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):
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
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
Uruchom Menedżera zadań systemu Windows.
Na karcie przyjrzyj
Performance
się zatwierdzonej wartości. (W systemie Windows 7 przyjrzyj sięCommit (KB)
w plikuSystem group
.)Jeśli wartość
Total
znajduje się bliskoLimit
, 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, dlategogcroot
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
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.
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
,Gen1
iGen2
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 (
GCSuspendEEEnd
–GCSuspendEEBegin_V1
).Rzeczywiste odzyskiwanie pamięci trwało 4,8 ms (
GCEnd_V1
–GCStart_V1
).Wznawianie zarządzanych wątków trwało 21us (
GCRestartEEEnd
–GCRestartEEBegin
).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 to1
. 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 tedouble[]
obiekty były powodem, dla którego doszło do odzyskiwania pamięci tej generacji 2. Następnym krokiem jest ustalenie, dlaczegodouble[]
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.