Podstawy odzyskiwania pamięci

W środowisku uruchomieniowym języka wspólnego (CLR) moduł odśmiecacz pamięci (GC) służy jako automatyczny menedżer 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 zapomnienie zwolnienia 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.

Korzyści

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 utrzymuje pamięć dostępną dla przyszłych alokacji. Obiekty zarządzane automatycznie pobierają czystą zawartość na początek, 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ą środowiska 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 komputerach 32-bitowych każdy proces ma 2 GB wirtualnej przestrzeni adresowej trybu użytkownika.

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

    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.
    Zarezerwowany 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 on zatwierdzony.
    Committed Blok pamięci jest przypisywany do magazynu fizycznego.
  • Wirtualna przestrzeń adresowa może być rozdrobniona, co oznacza, że w przestrzeni adresowej znajdują się wolne bloki nazywane otworami. 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 adresu.

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

    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, a następnie tworzy kopię zapasową niektórych danych 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 stosie. 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 pamięć do niej w przestrzeni adresowej bezpośrednio po pierwszym obiekcie. O ile 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 to niemal tak szybkie, jak przydzielanie pamięci ze stosu. Ponadto, ponieważ nowe obiekty, które są przydzielane kolejno, są stale przechowywane w zarządzanym 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 na stosie wątku, rejestry procesora CPU, dojścia GC i finalizowanie kolejki. 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 poprosić resztę środowiska uruchomieniowego o te korzenie. Moduł odśmiecający elementy bezużyteczne używa tej listy do utworzenia grafu zawierającego wszystkie obiekty dostępne z katalogów głównych.

Obiekty, które nie znajdują się na grafie, są nieosiągalne z katalogów głównych aplikacji. Moduł odśmiecacz 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óre nie są osiągalne. 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 za pomocą powiadomienia o niskiej ilości pamięci z systemu operacyjnego lub małej ilości pamięci wskazanej 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śmiecający elementy bezużyteczne działa 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 odśmieceń pamięci clR przydziela segment pamięci do przechowywania obiektów i zarządzania nimi. Ta pamięć jest nazywana stertą zarządzaną, a nie natywną stertą 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śmiecający pamięci jest specyficzny dla implementacji i może ulec zmianie w dowolnym momencie, w tym w okresowych aktualizacjach. Aplikacja nigdy nie powinna stosować założeń dotyczących 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śmiecany 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 obiekty martwe. Proces odzyskiwania kompakuje obiekty na żywo, aby były przenoszone razem, a martwa przestrzeń jest usuwana, dzięki czemu sterta jest mniejsza. Ten proces zapewnia, że obiekty przydzielone razem pozostają razem na zarządzanym stosie, 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.

Stertę można uznać za akumulację dwóch stert: stertę 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.

Porada

Rozmiar progu dla obiektów można skonfigurować do przechodzenia na dużą stertę obiektów.

Pokoleń

Algorytm GC opiera się na kilku zagadnieniach:

  • Szybciej kompaktować pamięć dla części zarządzanego 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ą powiązane ze sobą 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śmiecenia pamięci, zarządzana sterta jest podzielona na trzy generacje, 0, 1 i 2, dzięki czemu może obsługiwać obiekty długotrwałe i krótkotrwałe oddzielnie. Moduł odśmiecniający 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ż jest szybsze kompaktowanie części sterty zarządzanej niż cała sterta, ten schemat pozwala modułowi odśmiecania pamięci zwolnić pamięć w określonej generacji, a nie zwalniać pamięci dla całej sterty zarządzanej za każdym razem, gdy wykonuje kolekcję.

  • Pokolenie 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 generowania kolekcji 0. Jednak jeśli są to duże obiekty, przechodzą na dużą stertę obiektów (LOH), która jest czasami nazywana generacją 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ą 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 kontynuowanie tworzenia nowych obiektów.

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

    Gdy moduł odśmiecania pamięci wykona kolekcję generacji 0, zagęszcza 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 usuwać obiektów w pokoleniach 1 i 2 za każdym razem, gdy wykonuje kolekcję 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ć kolekcję 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 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 dużej stercie obiektów (nazywanej 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 jego młodszych pokoleniach. 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ą nazywane ocalałymi i są promowane do następnej generacji:

  • Obiekty, które przetrwają odzyskiwanie pamięci generacji 0, są promowane do generacji 1.
  • Obiekty, które przetrwają 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 pozwalając aplikacji na zbyt duże działanie zestawu roboczego, opóźniając odzyskiwanie pamięci i nie pozwalając na zbyt częste uruchamianie odzyskiwania pamięci.

Efemeryczne generacje i segmenty

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

Efemeryczne generacje są przydzielane w segmencie pamięci, który jest znany jako segment efemeryczny. Każdy nowy segment uzyskany przez moduł odśmiecający śmieci 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, a w przypadku typu modułu odśmiecenia pamięci jest uruchomiony (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
Kontroler domeny serwera z 4 logicznymi procesorami > CPU 32 MB 2 GB
Kontroler domeny serwera z 8 logicznymi procesorami > CPU 16 MB 1 GB

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

Ilość zwalnianej pamięci z efemerycznego odzyskiwania pamięci jest ograniczona do rozmiaru segmentu efemerycznego. Ilość pamięci, która jest zwolniona, jest proporcjonalna do miejsca zajmowanego przez martwe obiekty.

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 kompaktowa ocalałych obiektów. Faza kompaktowania przenosi obiekty, które przeżyły odzyskiwanie pamięci w kierunku starszego końca segmentu.

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

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

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

  • Stos root: zmienne stosu dostarczane 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.

  • Obsługa 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 zawieszenie innych wątków:

Zrzut ekranu przedstawiający sposób wyzwalania przez wątek odzyskiwania pamięci.

Zasoby niezarządzane

W przypadku większości obiektów tworzonych przez aplikację można automatycznie korzystać z odzyskiwania 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. Chociaż 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 oczyszczenia 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, pamiętaj o wywołaniu w Dispose razie potrzeby.

Należy również podać sposób na zwolnienie niezarządzanych zasobów 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 Czyszczenie niezarządzanych zasobów.

Zobacz też