Základy uvolňování paměti

V modulu CLR (Common Language Runtime) slouží uvolňování paměti (GC) jako správce automatické paměti. Systém uvolňování paměti spravuje přidělení a uvolnění paměti pro aplikaci. Vývojáři, kteří pracují se spravovaným kódem, proto nemusí psát kód pro provádění úloh správy paměti. Automatická správa paměti může eliminovat běžné problémy, jako je například zapomenutí uvolnit objekt a způsobit nevracení paměti nebo pokus o přístup k uvolněné paměti pro objekt, který je již uvolněn.

Tento článek popisuje základní koncepty uvolňování paměti.

Zaměstnanecké výhody

Systém uvolňování paměti poskytuje následující výhody:

  • Umožňuje vývojářům ručně uvolnit paměť.

  • Efektivně přiděluje objekty na spravované haldě.

  • Uvolní objekty, které se už nepoužívají, vymaže jejich paměť a zachová paměť dostupnou pro budoucí přidělení. Spravované objekty automaticky získají čistý obsah, takže jejich konstruktory nemusí inicializovat každé datové pole.

  • Poskytuje bezpečnost paměti tím, že zajistí, že objekt nemůže použít pro sebe paměť přidělenou jinému objektu.

Základy paměti

Následující seznam shrnuje důležité koncepty paměti CLR:

  • Každý proces má svůj vlastní samostatný virtuální adresní prostor. Všechny procesy na stejném počítači sdílejí stejnou fyzickou paměť a stránkační soubor, pokud existuje.

  • Ve výchozím nastavení má každý proces na 32bitových počítačích virtuální adresní prostor v uživatelském režimu 2 GB.

  • Jako vývojář aplikací pracujete jenom s virtuálním adresní prostorem a nikdy nebudete manipulovat s fyzickou pamětí přímo. Systém uvolňování paměti pro vás přidělí a uvolní virtuální paměť na spravované haldě.

    Pokud píšete nativní kód, můžete pomocí funkcí Systému Windows pracovat s virtuálním adresní prostorem. Tyto funkce přidělují a uvolní virtuální paměť pro vás na nativních haldách.

  • Virtuální paměť může být ve třech stavech:

    Stát Popis
    Free Blok paměti nemá žádné odkazy na něj a je k dispozici pro přidělení.
    Rezervováno Blok paměti je k dispozici pro vaše použití a nedá se použít pro žádné jiné žádosti o přidělení. Do tohoto bloku paměti ale nemůžete ukládat data, dokud nebudou potvrzena.
    Zavázala Blok paměti je přiřazen k fyzickému úložišti.
  • Virtuální adresní prostor se může fragmentovat, což znamená, že v adresní prostoru jsou volné bloky označované jako díry. Když se požaduje přidělení virtuální paměti, musí správce virtuální paměti najít jeden volný blok, který je dostatečně velký, aby vyhovoval žádosti o přidělení. I v případě, že máte 2 GB volného místa, přidělení, které vyžaduje 2 GB, nebude neúspěšné, pokud nebude všechny volné místo v jednom bloku adresy.

  • Pokud není dostatek virtuálního adresního prostoru k rezervaci nebo fyzickému prostoru k potvrzení, můžete nedostatek paměti.

    Stránkový soubor se používá i v případě, že je nízký zatížení fyzické paměti (poptávka po fyzické paměti). Při prvním vysokém zatížení fyzické paměti musí operační systém vytvořit místo ve fyzické paměti pro ukládání dat a zálohuje některá data, která jsou ve fyzické paměti, do stránkového souboru. Data nejsou stránkována, dokud nejsou potřeba, takže je možné narazit na stránkování v situacích, kdy je nízký tlak fyzické paměti.

Přidělení paměti

Při inicializaci nového procesu si modul runtime vyhrazuje souvislou oblast adresního prostoru procesu. Tento vyhrazený adresní prostor se nazývá spravovaná halda. Spravovaná halda udržuje ukazatel na adresu, kde bude přidělen další objekt v haldě. Zpočátku je tento ukazatel nastavený na základní adresu spravované haldy. Všechny odkazové typy se přidělují na spravované haldě. Když aplikace vytvoří první typ odkazu, paměť je přidělena pro typ na základní adrese spravované haldy. Když aplikace vytvoří další objekt, modul runtime pro něj přidělí paměť v adresního prostoru bezprostředně za prvním objektem. Pokud je adresní prostor k dispozici, modul runtime tímto způsobem přiděluje prostor pro nové objekty.

Přidělování paměti ze spravované haldy je rychlejší než přidělení nespravované paměti. Vzhledem k tomu, že modul runtime přiděluje paměť objektu přidáním hodnoty ukazatele, je téměř stejně rychlé jako přidělení paměti ze zásobníku. Kromě toho, protože nové objekty, které jsou přiděleny po sobě jdoucí, jsou uloženy souvisle ve spravované haldě, může aplikace přistupovat k objektům rychle.

Uvolnění paměti

Optimalizační modul uvolňování paměti určuje nejvhodnější čas k provedení kolekce na základě přidělení. Když uvolňování paměti provede kolekci, uvolní paměť pro objekty, které už aplikace nepoužívá. Určuje, které objekty se už nepoužívají prozkoumáním kořenového adresáře aplikace. Kořeny aplikace zahrnují statická pole, místní proměnné v zásobníku vlákna, registry procesoru, obslužné rutiny uvolňování paměti a konečnou frontu. Každý kořenový adresář buď odkazuje na objekt ve spravované haldě, nebo je nastaven na hodnotu null. Systém uvolňování paměti může požádat zbytek modulu runtime pro tyto kořeny. Systém uvolňování paměti používá tento seznam k vytvoření grafu, který obsahuje všechny objekty, které jsou dostupné z kořenového adresáře.

Objekty, které nejsou v grafu, jsou nedostupné z kořenového adresáře aplikace. Systém uvolňování paměti bere v úvahu nedostupné objekty uvolňování paměti a uvolní paměť přidělenou pro ně. Během shromažďování systém uvolňování paměti zkoumá spravovanou haldu a hledá bloky adresního prostoru obsazeného nedostupnými objekty. Když zjistí každý nedostupný objekt, využívá funkci kopírování paměti ke komprimování dosažitelných objektů v paměti a uvolní bloky adresních prostorů přidělených nedostupným objektům. Jakmile je paměť pro dosažitelné objekty komprimovaná, systém uvolňování paměti provede potřebné opravy ukazatelů tak, aby kořeny aplikace odkazovaly na objekty v jejich nových umístěních. Umístí také ukazatel spravované haldy za poslední dosažitelný objekt.

Paměť je komprimována pouze v případě, že kolekce zjistí významný počet nedostupných objektů. Pokud všechny objekty ve spravované haldě přežijí kolekci, není potřeba komprimovat paměť.

Aby se zlepšil výkon, modul runtime přidělí paměť pro velké objekty v samostatné haldě. Uvolňování paměti automaticky uvolní paměť pro velké objekty. Pokud se ale chcete vyhnout přesouvání velkých objektů v paměti, není tato paměť obvykle komprimovaná.

Podmínky pro uvolňování paměti

K uvolňování paměti dochází v případě, že platí jedna z následujících podmínek:

  • Systém má malou fyzickou paměť. Velikost paměti je zjištěna oznámením o nedostatku paměti z operačního systému nebo nedostatkem paměti, jak je uvedeno hostitelem.

  • Paměť, kterou používají přidělené objekty ve spravované haldě, překračuje přijatelnou prahovou hodnotu. Tato prahová hodnota se průběžně upravuje při spuštění procesu.

  • Volá se GC.Collect metoda. V téměř všech případech nemusíte tuto metodu volat, protože systém uvolňování paměti běží nepřetržitě. Tato metoda se primárně používá pro jedinečné situace a testování.

Spravovaná halda

Po inicializaci modulu CLR uvolňování paměti přidělí segment paměti pro ukládání a správu objektů. Tato paměť se nazývá spravovaná halda, nikoli nativní halda v operačním systému.

Pro každý spravovaný proces existuje spravovaná halda. Všechna vlákna procesu přidělují paměť pro objekty ve stejné haldě.

Pro rezervaci paměti systém uvolňování paměti volá funkci Windows VirtualAlloc a vyhrazuje si jeden segment paměti najednou pro spravované aplikace. Systém uvolňování paměti také podle potřeby rezervuje segmenty a uvolní segmenty zpět do operačního systému (po vymazání objektů) voláním funkce Windows VirtualFree .

Důležité

Velikost segmentů přidělených systémem uvolňování paměti je specifická pro implementaci a může se kdykoli změnit, včetně pravidelných aktualizací. Vaše aplikace by nikdy neměla provádět předpoklady týkající se konkrétní velikosti segmentu ani záviset na ní, ani by se neměla pokoušet konfigurovat množství paměti dostupné pro přidělení segmentů.

Čím méně objektů přidělených haldě, tím méně práce musí systém uvolňování paměti provést. Při přidělování objektů nepoužívejte zaokrouhlené hodnoty, které překračují vaše potřeby, například přidělení pole 32 bajtů, pokud potřebujete jenom 15 bajtů.

Když se aktivuje uvolňování paměti, uvolňování paměti uvolní paměť obsazenou mrtvými objekty. Proces uvolnění zkomprimuje živé objekty tak, aby se přesunuly dohromady, a mrtvý prostor se odebere, čímž se halda zmenší. Tento proces zajišťuje, aby objekty, které jsou přiděleny společně, zůstaly na spravované haldě, aby zachovaly svoji lokalitu.

Rušivost (frekvence a doba trvání) uvolňování paměti je výsledkem svazku přidělení a množství přeživené paměti ve spravované haldě.

Haldu lze považovat za akumulace dvou hald: velkou haldu objektu a malou haldu objektu. Velká halda objektu obsahuje objekty, které jsou 85 000 bajtů a větší, což jsou obvykle pole. Je vzácné, aby objekt instance byl extra velký.

Tip

Můžete nakonfigurovat velikost prahové hodnoty pro objekty, aby přešly na velkou haldu objektu.

Generace

Algoritmus GC je založen na několika aspektech:

  • Je rychlejší komprimovat paměť pro část spravované haldy než pro celou spravovanou haldu.
  • Novější objekty mají kratší životnost a starší objekty mají delší životnost.
  • Novější objekty se obvykle vzájemně souvisejí a aplikace k němu přistupuje přibližně ve stejnou dobu.

Uvolňování paměti se primárně vyskytuje při relamaci krátkodobých objektů. Pro optimalizaci výkonu uvolňování paměti je spravovaná halda rozdělena do tří generací, 0, 1 a 2, takže dokáže zpracovávat dlouhodobé a krátkodobé objekty samostatně. Systém uvolňování paměti ukládá nové objekty v generaci 0. Objekty vytvořené v rané fázi životnosti aplikace, které přežijí kolekce, se propagují a ukládají v generacích 1 a 2. Vzhledem k tomu, že je rychlejší zkomprimovat část spravované haldy než celá halda, umožňuje toto schéma uvolňování paměti uvolnit paměť v konkrétní generaci, a ne uvolnit paměť pro celou spravovanou haldu pokaždé, když provede kolekci.

  • Generace 0: Tato generace je nejmenší a obsahuje krátkodobé objekty. Příkladem krátkodobého objektu je dočasná proměnná. Uvolňování paměti probíhá nejčastěji v této generaci.

    Nově přidělené objekty tvoří novou generaci objektů a implicitně generují 0 kolekcí. Pokud jsou ale velké objekty, jdou na velkou haldu objektů (LOH), která se někdy označuje jako generace 3. Generace 3 je fyzická generace, která se logicky shromažďuje jako součást generace 2.

    Většinaobjektůch

    Pokud se aplikace pokusí vytvořit nový objekt, když je generace 0 plná, systém uvolňování paměti provede kolekci, aby uvolnil adresní prostor objektu. Systém uvolňování paměti začíná zkoumáním objektů ve generaci 0, nikoli všemi objekty ve spravované haldě. Kolekce generace 0 často uvolní dostatek paměti, aby aplikace mohla pokračovat ve vytváření nových objektů.

  • Generace 1: Tato generace obsahuje krátkodobé objekty a slouží jako vyrovnávací paměť mezi krátkodobými objekty a dlouhodobými objekty.

    Jakmile systém uvolňování paměti provede kolekci generace 0, zkomprimuje paměť pro dosažitelné objekty a podporuje je na generaci 1. Protože objekty, které přežijí kolekce, mají tendenci mít delší životnost, dává smysl je propagovat na vyšší generaci. Uvolňování paměti nemusí znovu zkoumat objekty v generacích 1 a 2 pokaždé, když provádí kolekci generace 0.

    Pokud kolekce generace 0 nevymaže dostatek paměti, aby aplikace vytvořila nový objekt, může uvolňování paměti provést kolekci 1. generace a 2. generace. Objekty v generaci 1, které přežijí kolekce, jsou povýšeny na generaci 2.

  • Generace 2: Tato generace obsahuje dlouhodobé objekty. Příkladem dlouhodobého objektu je objekt v serverové aplikaci, která obsahuje statická data, která jsou aktivní po dobu trvání procesu.

    Objekty v generaci 2, které přežijí kolekci, zůstanou v generaci 2, dokud nebudou v budoucí kolekci nedostupné.

    Objekty ve velké haldě objektu (která se někdy označuje jako generace 3) se shromažďují také v generaci 2.

Uvolňování paměti probíhá v konkrétních generacích jako podmínky. Shromažďování generace znamená shromažďování objektů v této generaci a všech jeho mladších generací. Uvolňování paměti generace 2 se také označuje jako úplné uvolňování paměti, protože uvolní objekty ve všech generacích (tj. všechny objekty ve spravované haldě).

Přežití a propagační akce

Objekty, které nejsou uvolněné v uvolňování paměti, se označují jako přeživší a jsou povýšeny na novou generaci:

  • Objekty, které přežijí uvolňování paměti generace 0, jsou povýšeny na generaci 1.
  • Objekty, které přežijí uvolňování paměti generace 1, jsou povýšeny na generaci 2.
  • Objekty, které přežijí uvolňování paměti generace 2, zůstanou ve generaci 2.

Když systém uvolňování paměti zjistí, že míra přežití je v generaci vysoká, zvýší prahovou hodnotu přidělení pro danou generaci. Další kolekce získá značnou velikost uvolněné paměti. CLR neustále vyrovnává dvě priority: neumožňuje, aby pracovní sada aplikace byla příliš velká, protože zpožďuje uvolňování paměti a neumožňuje příliš často spustit uvolňování paměti.

Dočasné generace a segmenty

Vzhledem k tomu, že objekty v generacích 0 a 1 jsou krátkodobé, tyto generace se označují jako dočasné generace.

Dočasné generace se přidělují v segmentu paměti, který se označuje jako dočasný segment. Každý nový segment získaný uvolňováním paměti se stane novým dočasným segmentem a obsahuje objekty, které přežily uvolňování paměti generace 0. Starý dočasný segment se stane novým segmentem generace 2.

Velikost dočasného segmentu se liší v závislosti na tom, jestli je systém 32bitový nebo 64bitový a na typu uvolňování paměti, na kterém běží (pracovní stanice nebo server GC). Následující tabulka uvádí výchozí velikosti dočasného segmentu:

Pracovní stanice/serverový GC 32bitová 64bitová
GC pracovní stanice 16 MB 256 MB
Server GC 64 MB 4 GB
Serverový GC se 4 logickými > procesory 32 MB 2 GB
Serverový GC s 8 logickými > procesory 16 MB 1 GB

Dočasný segment může obsahovat objekty generace 2. Objekty generace 2 můžou používat více segmentů, kolik váš proces vyžaduje, a paměť umožňuje.

Velikost uvolněné paměti z dočasného uvolňování paměti je omezená na velikost dočasného segmentu. Množství uvolněné paměti je úměrné prostoru, který byly obsazeny mrtvými objekty.

Co se stane během uvolňování paměti

Uvolňování paměti má následující fáze:

  • Fáze označení, která najde a vytvoří seznam všech živých objektů.

  • Změna umístění, která aktualizuje odkazy na objekty, které budou komprimovány.

  • Komprimující fáze, která uvolní prostor obsazený mrtvými objekty a zkomprimuje přeživší objekty. Fáze komprimace přesune objekty, které přežily uvolňování paměti směrem ke staršímu konci segmentu.

    Vzhledem k tomu, že kolekce generace 2 mohou zabírat více segmentů, lze objekty, které jsou povýšeny do generace 2, přesunuty do staršího segmentu. Přeživší generace 1 i generace 2 je možné přesunout do jiného segmentu, protože jsou povýšeny na generaci 2.

    Obvykle není halda velkého objektu (LOH) komprimovaná, protože kopírování velkých objektů představuje penalizaci výkonu. V .NET Core a v rozhraní .NET Framework 4.5.1 a novější však můžete pomocí GCSettings.LargeObjectHeapCompactionMode vlastnosti komprimovat haldu velkých objektů na vyžádání. Kromě toho je LOH automaticky komprimován, pokud je pevný limit nastaven zadáním:

Uvolňování paměti používá následující informace k určení, zda jsou objekty živé:

  • Stack root: Stack variables provided by the just-in-time compiler (JIT) compiler and stack walker. Optimalizace JIT můžou prodloužit nebo zkrátit oblasti kódu, ve kterých jsou proměnné zásobníku hlášeny systému uvolňování paměti.

  • Zpracování uvolňování paměti: Zpracovává objekty, které odkazují na spravované objekty a které je možné přidělit uživatelským kódem nebo modulem CLR (Common Language Runtime).

  • Statická data: Statické objekty v doménách aplikace, které můžou odkazovat na jiné objekty. Každá doména aplikace sleduje své statické objekty.

Před spuštěním uvolňování paměti jsou všechna spravovaná vlákna pozastavena s výjimkou vlákna, které aktivovalo uvolňování paměti.

Následující obrázek znázorňuje vlákno, které aktivuje uvolňování paměti a způsobí pozastavení ostatních vláken:

Screenshot of how a thread triggers a Garbage Collection.

Nespravované prostředky

U většiny objektů, které vaše aplikace vytvoří, můžete při automatickém provádění potřebných úloh správy paměti spoléhat na uvolňování paměti. Nespravované prostředky ale vyžadují explicitní vyčištění. Nejběžnějším typem nespravovaného prostředku je objekt, který zabalí prostředek operačního systému, například popisovač souboru, popisovač okna nebo síťové připojení. Přestože systém uvolňování paměti může sledovat životnost spravovaného objektu, který zapouzdřuje nespravovaný prostředek, nemá konkrétní znalosti o vyčištění prostředku.

Když definujete objekt, který zapouzdřuje nespravovaný prostředek, doporučuje se zadat potřebný kód pro vyčištění nespravovaného prostředku ve veřejné Dispose metodě. Poskytnutím Dispose metody povolíte uživatelům objektu explicitně uvolnit prostředek, jakmile s objektem skončí. Pokud používáte objekt, který zapouzdřuje nespravovaný prostředek, nezapomeňte volat Dispose podle potřeby.

Musíte také poskytnout způsob, jak se nespravované prostředky uvolní v případě, že uživatel vašeho typu zapomene volat Dispose. K zabalení nespravovaného prostředku můžete použít bezpečný popisovač nebo přepsat metodu Object.Finalize() .

Další informace o čištění nespravovaných prostředků naleznete v tématu Vyčištění nespravovaných prostředků.

Viz také