Garbage Collector–część II

W poprzednim poście przedstawiłem ogólne zasady działania GC w zarządzanych językach. Dziś przyjrzymy się bardziej na konstrukcję rozwiązania Microsoft’owego. Zakładam, że czytelnik zna już algorytm Mark&Sweep. Pamięć alokowana w .NET jest przechowywana w tzw. generacjach. Istnieją 3 generacje:

  1. Generation 0 – zwolnienie obiektu z GEN0 jest szybkie i mało kosztowne. Przechowywane są w niej obiekty używane tylko przez krótki czas.
  2. Generation 1  – obiekty, które awansowały z GEN0. Zwolnienie zasobów w GEN1 jest wolniejsze.
  3. Generation 2 – analogicznie do GEN1 – usuwanie zasobów jest wolniejsze niż ma to miejsce w GEN0 lub GEN1.

.NET zatem, ze względów wydajnościowych, przydziela zasoby do różnych generacji. Jeśli obiekt jest używany przez krótki czas a potem zwalniany, ma to wtedy miejsce w GEN0. Jeśli podczas zwalniania zasobów, czyli w momencie wywołania metody GC.Collect(), któreś z obiektów przetrwają wtedy awansowane są do następnej generacji.  GC analizuje ile razy obiekt musi przetrwać wywołanie GC.Collect aby zostać awansowany do następnej generacji. Wartości tych progów przydziale są przez GC dynamicznie tak aby było to maksymalnie optymalne.

Jeśli np. obiekt nie został zwolniony podczas 10 GC.Collect (Mark&Sweep), wtedy bardzo prawdopodobne jest, że przez większość czasu działania aplikacji będzie on potrzebny – a co za tym idzie szkoda marnować czasu na mieszanie go z obiektami krótkotrwałymi. Ktoś może zadać pytanie, ale co za problem trzymać wszystkie zasoby w jednej generacji. GC po każdym zwalnianiu pamięci porządkuje zasoby tak, że nie ma pomiędzy obiektem A a obiektem B wolnej przestrzeni. Jeśli obiektA zużywa zasoby pomiędzy adresem 0-4, to obiektB będzie zajmował od 5-9. Dzięki temu, deklaracja nowych obiektów jest szybka ponieważ bardzo łatwo wyznaczyć adres w pamięci – jest to po prostu następna liczba (w naszym przypadku obiekt3 byłby zadeklarowany w polu 10). Unikamy również problemu fragmentacji. Obiekty długotrwałe powodowałyby problem ponieważ należałoby je “przesuwać” po każdym GC.Collect oraz po co wykonywać algorytm Mark&Sweep dla drzewa obiektów, które jest potrzebne przez 90% czasu działania aplikacji?

Podsumowując:

  1. W celu optymalizacji wprowadzono 3 generacje. GEN0 jest szybka i zawiera obiekty często deklarowane i zwalniane.
  2. Pamięć jest deklarowana w sposób zwarty – nie ma wolnej przestrzeni między obiektami (brak fragmentacji).
  3. Obiekty w tej samej generacji są mniej więcej używane w aplikacji przez taki sam czas.
  4. Tylko część pamięci jest przeznaczona dla aplikacji .NET (tzw. commited memory space). Jeśli potrzeba więcej wtedy system operacyjny z “uncommited space” przydziela pamięć do “commited space”.
  5. GC.Collect uruchamiany jest gdy pewien próg zużycia pamięci zostanie przekroczony – a nie natychmiast gdy któryś z obiektów jest już niepotrzebny.

Posiadając już wiedzę o generacjach, w następny poście wyjaśnię co to jest tzw. Healthy GC.

3 thoughts on “Garbage Collector–część II”

  1. Nie wspomniałeś nic o LOH (Large Object Heap), na którą trafiają naprawdę duże obiekty (>= 85KB) i która jest czyszczona dopiero przy pełnym cyklu. Warto o tym pamiętać.

    Przy okazji. Nieoceniony przy profilowaniu pamięci jest darmowy CLR Profiler. Polecam zobaczyć na własne oczy jak pozornie niegroźna pętla np. ze sklejaniem stringów potrafi zaalokować i zwolnić w czasie wykonania kilkaset MB pamięci 🙂

Leave a Reply

Your email address will not be published.