Aby pisać kod, który jest wydajny i optymalny należy dobrze zrozumieć jak działa Garbage Collector (GC). W dzisiejszym poście przedstawię ogólne zasady działania GC na przykładzie algorytmu mark and sweep. Zaznaczam, że implementacja w .NET różni się i jest dużo bardziej wyrafinowana – ale o tym w następnych postach. Chcę najpierw przedstawić algorytm mark and sweep ponieważ da to czytelnikowi ogólny obraz zagadnienia związanego ze zwalnianiem pamięci w językach zarządzanych.
Garbage Collector oczywiście służy do zwalniania pamięci i wyręcza nas z pamiętania jakie zasoby już nie są potrzebne. Oczywiście czasami zachodzi potrzeba ręcznego zwolnienia pamięci ale o tym w przyszłych postach. Wielu z nas pewnie pamięta operator delete z CPP czy free z C. W językach zarządzanych wytropieniem niepotrzebnych obiektów zajmuję się Garbage Collector.
Podstawowy algorytm mark and sweep sprowadza się do dwóch faz: mark (znakowanie) oraz sweep (usuwanie). Najpierw (faza mark), począwszy od korzeni, wszystkie obiekty są znakowane jakąś flagą. Potem w fazie sweep, wszystkie NIE oznaczone obiekty są usuwane. Faza MARK służy zatem do znalezienia potrzebnych, używanych obiektów. Skoro można dotrzeć do jakiegoś obiektu od korzenia, to znaczy, że jest on używany (seria referencji). Korzeniem mogą być na przykład zmienne globalne.
Algorytm w pseudokodzie można naszkicować następująco:
void Collect() { // Faza I for each root (r) Mark (r); // Faza II Sweep (); }
Metoda Mark jest oczywiście rekurencyjna – jako parametr przyjmuje wskaźnik do korzenia a następnie przeszukuje całe drzewo obiektów. Spróbujmy naszkicować metodę Mark:
void Mark (object objectPointer) { if (objectPointer.IsMarked == false) { objectPointer.IsMarked = true; for each Object child of objectPointer) { Mark(child); } } }
Należy sprawdzać czy obiekt jest już oznakowany aby uniknąć zapętleń. Przyjrzymy się drugiej fazie (sweep):
void Sweep () { foreach objectPointer in the heap { if (objectPointer.IsMarked) { objectPointer.IsMarked = false } else { Release(objectPointer); } } }
Proszę zwrócić uwagę, że ustawiamy flagę na false – dzięki temu przy następnym uruchomieniu GC, będzie możliwa pierwsza faza. Wszystkie obiekty nieoznakowane są usuwane z pamięci.
Oczywiście GC nie zwalnia programisty z myślenia . Bardzo łatwo popełnić błąd i spowodować Memory Leak – o tym w ostatnim poście.