Podstawy odzyskiwania pamięci

W środowisku uruchomieniowym języka wspólnego (CLR) moduł odśmiecacz pamięci (GC) służy jako menedżer automatycznej pamięci. Moduł odśmieceń pamięci zarządza alokacją i zwalnianiem pamięci dla aplikacji. W związku z tym deweloperzy pracujący z kodem zarządzanym nie muszą pisać kodu w celu wykonywania zadań zarządzania pamięcią. Automatyczne zarządzanie pamięcią może wyeliminować typowe problemy, takie jak zapominanie o zwalnianiu obiektu i spowodowanie przecieku pamięci lub próba uzyskania dostępu do zwolnionej pamięci dla obiektu, który został już zwolniony.

W tym artykule opisano podstawowe pojęcia dotyczące odzyskiwania pamięci.

Świadczenia

Moduł odśmiecający elementy bezużyteczne zapewnia następujące korzyści:

  • Zwalnia deweloperów od konieczności ręcznego wydawania pamięci.

  • Efektywnie przydziela obiekty na zarządzanym stercie.

  • Odzyskuje obiekty, które nie są już używane, czyści ich pamięć i przechowuje pamięć dostępną dla przyszłych alokacji. Obiekty zarządzane automatycznie uzyskują czystą zawartość, aby rozpocząć od, więc ich konstruktory nie muszą inicjować każdego pola danych.

  • Zapewnia bezpieczeństwo pamięci, upewniając się, że obiekt nie może używać dla siebie pamięci przydzielonej dla innego obiektu.

Podstawy pamięci

Poniższa lista zawiera podsumowanie ważnych pojęć związanych z pamięcią CLR:

  • Każdy proces ma własną, oddzielną wirtualną przestrzeń adresową. Wszystkie procesy na tym samym komputerze współdzielą tę samą pamięć fizyczną i plik strony, jeśli istnieje.

  • Domyślnie na 32-bitowych komputerach każdy proces ma wirtualną przestrzeń adresową trybu użytkownika 2 GB.

  • Jako deweloper aplikacji pracujesz tylko z wirtualną przestrzenią adresową i nigdy nie manipulujesz pamięcią fizyczną bezpośrednio. Moduł odśmieceń pamięci przydziela i zwalnia pamięć wirtualną dla Ciebie na zarządzanym stercie.

    Jeśli piszesz kod natywny, używasz funkcji systemu Windows do pracy z wirtualną przestrzenią adresową. Te funkcje przydzielają i zwalniają pamięć wirtualną natywnych sterty.

  • Pamięć wirtualna może znajdować się w trzech stanach:

    Stan opis
    Bezpłatna Blok pamięci nie zawiera odwołań do niego i jest dostępny do alokacji.
    Zarezerwowana Blok pamięci jest dostępny do użycia i nie może być używany dla żadnego innego żądania alokacji. Nie można jednak przechowywać danych w tym bloku pamięci, dopóki nie zostanie zatwierdzona.
    Popełnione Blok pamięci jest przypisywany do magazynu fizycznego.
  • Wirtualna przestrzeń adresowa może być rozdrobniona, co oznacza, że istnieją wolne bloki znane jako otwory w przestrzeni adresowej. Po zażądaniu alokacji pamięci wirtualnej menedżer pamięci wirtualnej musi znaleźć pojedynczy wolny blok, który jest wystarczająco duży, aby spełnić żądanie alokacji. Nawet jeśli masz 2 GB wolnego miejsca, alokacja, która wymaga 2 GB, nie powiedzie się, chyba że wszystkie wolne miejsce znajduje się w jednym bloku adresowym.

  • Możesz zabrakło pamięci, jeśli nie ma wystarczającej wirtualnej przestrzeni adresowej, aby zarezerwować lub zatwierdzć przestrzeń fizyczną.

    Plik stronicowy jest używany nawet wtedy, gdy wykorzystanie pamięci fizycznej (zapotrzebowanie na pamięć fizyczną) jest niskie. Po raz pierwszy wysokie wykorzystanie pamięci fizycznej system operacyjny musi przechowywać dane w pamięci fizycznej i tworzy kopię zapasową niektórych danych, które znajdują się w pamięci fizycznej do pliku stronicowania. Dane nie są stronicowane, dopóki nie będą potrzebne, więc można napotkać stronicowanie w sytuacjach, w których wykorzystanie pamięci fizycznej jest niskie.

Alokacja pamięci

Podczas inicjowania nowego procesu środowisko uruchomieniowe rezerwuje dla niego ciągły region przestrzeni adresowej. Zarezerwowana przestrzeń adresowa jest nazywana „zarządzanym stosem”. Zarządzany stos zawiera wskaźnik do adresu, w którym zostanie przydzielony następny obiekt ze stosu. Początkowo wskaźnik jest ustawiony na adres podstawowy zarządzanego stosu. Wszystkie typy odwołań są przydzielane na zarządzanym stercie. Gdy aplikacja tworzy pierwszy typ referencyjny, jest dla niego rezerwowana pamięć pod podstawowym adresem zarządzanego stosu. Gdy aplikacja utworzy następny obiekt, środowisko uruchomieniowe przydziela dla niego pamięć w przestrzeni adresowej bezpośrednio po pierwszym obiekcie. Jeśli przestrzeń adresowa jest dostępna, środowisko uruchomieniowe nadal przydziela miejsce dla nowych obiektów w ten sposób.

Przydzielanie pamięci z zarządzanego stosu jest szybsze niż niezarządzane przydzielanie pamięci. Ponieważ środowisko uruchomieniowe przydziela pamięć dla obiektu przez dodanie wartości do wskaźnika, jest niemal tak szybko, jak przydzielanie pamięci ze stosu. Ponadto, ponieważ nowe obiekty, które są przydzielane kolejno, są przechowywane stale w zarządzanej stercie, aplikacja może szybko uzyskać dostęp do obiektów.

Wydanie pamięci

Aparat optymalizacji w module odśmiecania pamięci ustala najlepszy moment na wykonanie procesu wyrzucania w oparciu o dokonywane przydziały. Gdy moduł odśmiecania wykonuje ten proces, zwalnia pamięć zajmowaną przez obiekty, które nie są już używane przez aplikację. Określa, które obiekty nie są już używane, sprawdzając korzenie aplikacji. Katalogi głównych aplikacji obejmują pola statyczne, zmienne lokalne w stosie wątku, rejestry procesora CPU, uchwyty GC i kolejkę finalizowania. Każdy obiekt główny odwołuje się do obiektu w zarządzanym stosie albo przyjmuje wartość null. Moduł odśmieceń pamięci może zapytać resztę środowiska uruchomieniowego o te elementy root. Moduł odśmieceń pamięci używa tej listy do utworzenia grafu zawierającego wszystkie obiekty, które są osiągalne z korzeni.

Obiekty, które nie znajdują się na grafie, są nieosiągalne z katalogów głównych aplikacji. Moduł odśmiecywania pamięci uwzględnia nieosiągalne obiekty bezużyteczne i zwalnia przydzieloną im pamięć. Podczas wyrzucania elementów moduł odśmiecania pamięci analizuje zarządzany stos w poszukiwaniu bloków przestrzeni adresowej zajmowanych przez nieosiągalne obiekty. Po wykryciu niedostępnego obiektu moduł odśmiecania za pomocą funkcji kopiowania pamięci kompaktuje dostępne obiekty wewnątrz pamięci i jednoczenie zwalnia bloki przestrzeni adresowej przydzielone dotychczas nieosiągalnym obiektom. Po skompaktowaniu pamięci osiągalnych obiektów moduł odśmiecania pamięci dokonuje niezbędnych korekt wskaźników, tak aby obiekty główne aplikacji wskazywały obiekty w ich nowych lokalizacjach. Ponadto ustawia wskaźnik zarządzanego stosu za ostatnim dostępnym obiektem.

Pamięć jest kompaktowana tylko wtedy, gdy kolekcja odnajduje znaczną liczbę obiektów, których nie można osiągać. Jeśli wszystkie obiekty w zarządzanym stercie przetrwają kolekcję, nie ma potrzeby kompaktowania pamięci.

W celu poprawy wydajności działania środowisko uruchomieniowe przydziela pamięć dużym obiektom w oddzielnym stosie. Moduł odśmiecania pamięci automatycznie zwalnia pamięć dla dużych obiektów. Jednak aby uniknąć przenoszenia dużych obiektów w pamięci, ta pamięć zwykle nie jest kompaktowana.

Warunki odzyskiwania pamięci

Odzyskiwanie pamięci występuje, gdy spełniony jest jeden z następujących warunków:

  • System ma małą ilość pamięci fizycznej. Rozmiar pamięci jest wykrywany przez powiadomienie o niskiej ilości pamięci z systemu operacyjnego lub za mało pamięci wskazane przez hosta.

  • Pamięć używana przez przydzielone obiekty na zarządzanym stercie przekracza akceptowalny próg. Ten próg jest stale dostosowywany podczas uruchamiania procesu.

  • Wywoływana GC.Collect jest metoda . W prawie wszystkich przypadkach nie trzeba wywoływać tej metody, ponieważ moduł odśmiecacza pamięci jest uruchamiany w sposób ciągły. Ta metoda jest używana głównie w przypadku unikatowych sytuacji i testowania.

Zarządzana sterta

Po zainicjowaniu modułu odzyskiwania pamięci clR przydziela segment pamięci do przechowywania obiektów i zarządzania nimi. Ta pamięć jest nazywana zarządzaną stertą, w przeciwieństwie do natywnej sterty w systemie operacyjnym.

Dla każdego zarządzanego procesu istnieje zarządzana sterta. Wszystkie wątki w procesie przydzielają pamięć dla obiektów na tej samej stercie.

Aby zarezerwować pamięć, moduł odśmiecenia pamięci wywołuje funkcję Windows VirtualAlloc i rezerwuje jeden segment pamięci naraz dla zarządzanych aplikacji. Moduł odśmiecania pamięci rezerwuje również segmenty w razie potrzeby i zwalnia segmenty z powrotem do systemu operacyjnego (po wyczyszczeniu ich z dowolnych obiektów) przez wywołanie funkcji Windows VirtualFree .

Ważne

Rozmiar segmentów przydzielonych przez moduł odśmieceń pamięci 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.

Mniej obiektów przydzielonych na stercie, tym mniej pracy moduł odśmieceń pamięci musi wykonać. Podczas przydzielania obiektów nie należy używać zaokrąglonych wartości przekraczających potrzeby, takich jak przydzielanie tablicy 32 bajtów, gdy potrzebujesz tylko 15 bajtów.

Po wyzwoleniu odzyskiwania pamięci moduł odśmiecający pamięć zajmowaną przez martwe obiekty. Proces odzyskiwania kompaktuje obiekty na żywo, dzięki czemu są przenoszone razem, a martwe miejsce jest usuwane, dzięki czemu sterta jest mniejsza. Ten proces zapewnia, że obiekty przydzielone razem pozostają razem na zarządzanym stercie, aby zachować ich lokalność.

Natrętność (częstotliwość i czas trwania) odzyskiwania pamięci jest wynikiem ilości alokacji i ilości pamięci przetrwanej na zarządzanym stercie.

Sterta można uznać za akumulację dwóch stert: sterta dużego obiektu i stertę małego obiektu . Sterta dużych obiektów zawiera obiekty o rozmiarze 85 000 bajtów i większe, które są zwykle tablicami. Rzadko zdarza się, aby obiekt wystąpienia był bardzo duży.

Napiwek

Rozmiar progu dla obiektów można skonfigurować tak, aby przechodziły na stercie dużych obiektów.

Pokoleń

Algorytm GC opiera się na kilku zagadnieniach:

  • Szybsze jest kompaktowanie pamięci dla części zarządzanej sterty niż dla całej zarządzanej sterty.
  • Nowsze obiekty mają krótsze okresy istnienia, a starsze obiekty mają dłuższe okresy istnienia.
  • Nowsze obiekty zwykle są ze sobą powiązane i uzyskiwane przez aplikację w tym samym czasie.

Odzyskiwanie pamięci odbywa się głównie w przypadku odzyskiwania krótkotrwałych obiektów. Aby zoptymalizować wydajność modułu odśmiecanie pamięci, zarządzana sterta jest podzielona na trzy generacje, 0, 1 i 2, dzięki czemu może obsługiwać długotrwałe i krótkotrwałe obiekty oddzielnie. Moduł odśmieceń pamięci przechowuje nowe obiekty w generacji 0. Obiekty utworzone wcześniej w okresie istnienia aplikacji i nieusunięte przez moduł odśmiecania trafiają na wyższy poziom, do generacji 1 i 2. Ponieważ kompaktowanie części zarządzanej sterty jest szybsze niż cały sterta, ten schemat pozwala modułowi odśmiecania pamięci zwolnić pamięć w określonej generacji, a nie zwolnić pamięć dla całej zarządzanej sterty za każdym razem, gdy wykonuje kolekcję.

  • Generacja 0: To pokolenie jest najmłodsze i zawiera krótkotrwałe obiekty. Przykładem krótkotrwałego obiektu jest zmienna tymczasowa. Odzyskiwanie pamięci występuje najczęściej w tej generacji.

    Nowo przydzielone obiekty tworzą nową generację obiektów i są niejawnie generacji kolekcji 0. Jeśli jednak są dużymi obiektami, przechodzą na dużą stertę obiektów (LOH), która jest czasami określana jako generacja 3. Generacja 3 to generacja fizyczna, która jest logicznie zbierana w ramach generacji 2.

    Większość obiektów jest odzyskiwane w celu odzyskiwania pamięci w generacji 0 i nie przetrwa do następnej generacji.

    Jeśli aplikacja próbuje utworzyć nowy obiekt, gdy generacja 0 jest pełna, moduł odśmiecenia pamięci wykonuje kolekcję, aby zwolnić przestrzeń adresową dla obiektu. Rozpoczyna od zbadania obiektów w generacji 0, a nie wszystkich obiektów w zarządzanym stosie. Kolekcja samej generacji 0 często odzyskuje wystarczającą ilość pamięci, aby umożliwić aplikacji dalsze tworzenie nowych obiektów.

  • Generacja 1. Ta generacja zawiera obiekty krótkotrwałe i służy jako bufor między obiektami krótkotrwałymi i obiektami długotrwałymi.

    Gdy moduł odśmiecania pamięci wykonuje zbieranie generacji 0, kompakuje pamięć dla obiektów osiągalnych i promuje je do generacji 1. Ponieważ obiekty, które pozostały po procesie wyrzucania elementów, zwykle mają dłuższe okresy istnienia, podwyższenie im poziomu o generację jest jak najbardziej uzasadnione. Moduł odśmiecenia pamięci nie musi ponownie przeanalizować obiektów w pokoleniach 1 i 2 za każdym razem, gdy wykonuje zbieranie generacji 0.

    Jeśli kolekcja generacji 0 nie odzyska wystarczającej ilości pamięci dla aplikacji, aby utworzyć nowy obiekt, moduł odśmiecenia pamięci może wykonać zbieranie generacji 1, a następnie generacji 2. Obiekty w generacji 1, które nie zostały wyrzucone, są przenoszone do generacji 2.

  • Generacja 2. Ta generacja zawiera długotrwałe obiekty. Przykładem długotrwałego obiektu jest obiekt w aplikacji serwera, który zawiera dane statyczne, które są aktywne przez cały czas trwania procesu.

    Obiekty w generacji 2, które przetrwają kolekcję, pozostają w generacji 2, dopóki nie będą one osiągalne w przyszłej kolekcji.

    Obiekty na stercie dużych obiektów (nazywanych czasami generacją 3) są również zbierane w generacji 2.

Odzyskiwanie pamięci odbywa się w określonych generacjach jako uzasadnione warunki. Zbieranie pokolenia oznacza zbieranie obiektów w tym pokoleniu i wszystkich młodszych pokoleń. Odzyskiwanie pamięci generacji 2 jest również nazywane pełnym odzyskiwaniem pamięci, ponieważ odzyskuje obiekty we wszystkich generacjach (czyli wszystkie obiekty w zarządzanym stercie).

Przetrwanie i promocje

Obiekty, które nie są odzyskiwane w odzyskiwaniu pamięci, są znane jako ocalałych i są promowane do następnej generacji:

  • Obiekty, które przeżyją odzyskiwanie pamięci generacji 0, są promowane do generacji 1.
  • Obiekty, które przeżyją odzyskiwanie pamięci generacji 1, są promowane do generacji 2.
  • Obiekty, które przetrwają odzyskiwanie pamięci generacji 2, pozostają w generacji 2.

Gdy moduł odśmiecenia pamięci wykryje, że wskaźnik przetrwania jest wysoki w pokoleniu, zwiększa próg alokacji dla tego pokolenia. Następna kolekcja pobiera znaczną ilość odzyskanej pamięci. ClR stale równoważy dwa priorytety: nie pozwala aplikacji na zbyt duże pobieranie zestawu roboczego, opóźniając odzyskiwanie pamięci i nie pozwalając na zbyt częste uruchamianie odzyskiwania pamięci.

Efemeryczne pokolenia i segmenty

Ponieważ obiekty w pokoleniach 0 i 1 są krótkotrwałe, te pokolenia są znane jako efemeryczne pokolenia.

Generacje efemeryczne są przydzielane w segmencie pamięci, który jest znany jako segment efemeryczny. Każdy nowy segment uzyskany przez moduł odśmieceń pamięci staje się nowym segmentem efemerycznym i zawiera obiekty, które przeżyły odzyskiwanie pamięci generacji 0. Stary segment efemeryczny staje się segmentem nowej generacji 2.

Rozmiar segmentu efemerycznego różni się w zależności od tego, czy system jest 32-bitowy, czy 64-bitowy, oraz od typu modułu odśmiecającego śmieci, na którym działa (stacja robocza lub serwer GC). W poniższej tabeli przedstawiono domyślne rozmiary segmentu efemerycznego:

Stacja robocza/serwer GC 32-bitowa 64-bitowa
Stacja robocza GC 16 MB 256 MB
GC serwera 64 MB 4 GB
Serwer GC z 4 procesorami logicznymi > 32 MB 2 GB
Serwer GC z 8 procesorami logicznymi > 16 MB 1 GB

Segment efemeryczny może zawierać obiekty generacji 2. Obiekty generacji 2 mogą używać wielu segmentów, ile wymaga proces, a pamięć umożliwia.

Ilość wolnej pamięci z efemerycznego odzyskiwania pamięci jest ograniczona do rozmiaru segmentu efemerycznego. Ilość wolnej pamięci jest proporcjonalna do miejsca zajmowanego przez obiekty martwe.

Co się dzieje podczas odzyskiwania pamięci

Odzyskiwanie pamięci ma następujące fazy:

  • Faza oznaczania, która znajduje i tworzy listę wszystkich obiektów na żywo.

  • Faza przenoszenia, która aktualizuje odwołania do obiektów, które zostaną skompaktowane.

  • Faza kompaktowania, która odzyskuje miejsce zajmowane przez martwe obiekty i kompaktuje ocalałych obiektów. Faza kompaktowania przenosi obiekty, które przetrwały odzyskiwanie pamięci w kierunku starszego końca segmentu.

    Ponieważ kolekcje generacji 2 mogą zajmować wiele segmentów, obiekty awansowane do generacji 2 można przenosić do starszego segmentu. Zarówno generacji 1, jak i 2 ocalałych można przenieść do innego segmentu, ponieważ są one promowane do generacji 2.

    Zazwyczaj duża sterta obiektu (LOH) nie jest kompaktowana, ponieważ kopiowanie dużych obiektów nakłada karę za wydajność. Jednak w programie .NET Core i w programie .NET Framework 4.5.1 lub nowszym można użyć GCSettings.LargeObjectHeapCompactionMode tej właściwości, aby skompaktować duże sterty obiektów na żądanie. Ponadto LOH jest automatycznie kompaktowany, gdy limit twardy jest ustawiany przez określenie jednego z następujących elementów:

Moduł odśmieceń pamięci używa następujących informacji, aby określić, czy obiekty są aktywne:

  • Katalogi głównych stosu: zmienne stosu udostępniane przez kompilator just in time (JIT) i przewodnik stosu. Optymalizacje JIT mogą wydłużyć lub skrócić regiony kodu, w których zmienne stosu są zgłaszane do modułu odśmiecania pamięci.

  • Dojście do odzyskiwania pamięci: obsługuje obiekty zarządzane, które mogą być przydzielane przez kod użytkownika lub środowisko uruchomieniowe języka wspólnego.

  • Dane statyczne: obiekty statyczne w domenach aplikacji, które mogą odwoływać się do innych obiektów. Każda domena aplikacji śledzi swoje obiekty statyczne.

Przed rozpoczęciem odzyskiwania pamięci wszystkie zarządzane wątki są zawieszone z wyjątkiem wątku, który wyzwolił odzyskiwanie pamięci.

Na poniższej ilustracji przedstawiono wątek, który wyzwala odzyskiwanie pamięci i powoduje wstrzymanie innych wątków:

Screenshot of how a thread triggers a Garbage Collection.

Zasoby niezarządzane

W przypadku większości obiektów tworzonych przez aplikację można polegać na usuwaniu pamięci w celu automatycznego wykonywania niezbędnych zadań zarządzania pamięcią. Zasoby niezarządzane wymagają jednak jawnego usuwania z pamięci. Najpopularniejszym typem niezarządzanego zasobu jest obiekt opakowujący zasób systemu operacyjnego, taki jak dojście do pliku, uchwyt okna lub połączenie sieciowe. Mimo że moduł odśmiecanie pamięci może śledzić okres istnienia zarządzanego obiektu, który hermetyzuje niezarządzany zasób, nie ma konkretnej wiedzy na temat sposobu czyszczenia zasobu.

Podczas definiowania obiektu, który hermetyzuje niezarządzany zasób, zaleca się podanie kodu niezbędnego do wyczyszczenia niezarządzanego zasobu w metodzie publicznej Dispose . Udostępniając metodę Dispose , można zezwolić użytkownikom obiektu na jawne zwolnienie zasobu po zakończeniu pracy z obiektem. Jeśli używasz obiektu, który hermetyzuje niezarządzany zasób, upewnij się, że wywołaj Dispose je w razie potrzeby.

Należy również zapewnić sposób, w jaki zasoby niezarządzane mają być zwalniane w przypadku, gdy użytkownik typu zapomni wywołać metodę Dispose. Możesz użyć bezpiecznego uchwytu, aby opakowować niezarządzany zasób lub zastąpić metodę Object.Finalize() .

Aby uzyskać więcej informacji na temat czyszczenia zasobów niezarządzanych, zobacz Oczyszczanie niezarządzanych zasobów.

Zobacz też