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. Uvolňování paměti spravuje přidělení a uvolnění paměti pro aplikaci. Pro vývojáře pracující se spravovaným kódem to znamená, že nemusíte 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, například zapomenout uvolnit objekt a způsobit nevracení paměti nebo pokus o přístup k paměti pro objekt, který je již uvolněn.

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

Výhody

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 ve 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ískávají čistý obsah, aby začínaly, takže jejich konstruktory nemusí inicializovat každé datové pole.

  • Poskytuje bezpečnost paměti tím, že zajišťuje, ž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ánkový 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 pracovat s fyzickou pamětí přímo. Uvolňování paměti vám přidělí a uvolní virtuální paměť na spravované haldě.

    Pokud píšete nativní kód, můžete pomocí funkcí 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:

    State Popis
    Free Blok paměti na něj nemá žádné odkazy a je k dispozici pro přidělení.
    Vyhrazené Blok paměti je k dispozici pro vaše použití a nedá se použít pro žádný jiný požadavek na přidělení. Do tohoto bloku paměti však nelze ukládat data, dokud se nespíší.
    Committed Blok paměti je přiřazen k fyzickému úložišti.
  • Virtuální adresní prostor se může fragmentovat. To znamená, že v adresní prostoru jsou volné bloky, označované také 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 vyhověl požadavkům na přidělení. I když máte 2 GB volného místa, přidělení, které vyžaduje 2 GB, nebude úspěšné, pokud není veškerý tento volný prostor v jednom bloku adresy.

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

    Stránkový soubor se používá i v případě, že je nízký fyzický tlak na paměť (tj. 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. Tato data nejsou stránkována, dokud je nepotřebujete, 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 se přidělí další objekt v haldě. Zpočátku je tento ukazatel nastavený na základní adresu spravované haldy. Všechny odkazové typy se přidělují ve spravované haldě. Když aplikace vytvoří první typ odkazu, paměť se přidělí pro typ na základní adrese spravované haldy. Když aplikace vytvoří další objekt, uvolňování paměti pro něj přidělí paměť v adresní prostoru bezprostředně za prvním objektem. Pokud je adresní prostor dostupný, systém uvolňování paměti bude tímto způsobem dál přidělovat prostor pro nové objekty.

Přidělení 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 do 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

Optimalizátor systému 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řenů 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 finalizaci fronty. Každý kořen 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. Pomocí tohoto seznamu vytvoří systém uvolňování paměti graf, který obsahuje všechny objekty, které jsou dostupné z kořenového adresáře.

Objekty, které nejsou v grafu, nejsou z kořenů aplikace nedostupné. Uvolňování paměti považuje za nedostupné objekty uvolňování paměti a uvolní paměť přidělenou pro ně. Během kolekce 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, pomocí funkce kopírování paměti komprimuje dosažitelné objekty 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á, uvolňování paměti provede potřebné opravy ukazatele 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žily kolekci, není nutné komprimovat paměť.

Za účelem zvýšení výkonu modul runtime přidělí paměť pro velké objekty v samostatné haldě. Uvolňování paměti automaticky uvolní paměť pro velké objekty. Aby se však zabránilo přesouvání velkých objektů v paměti, tato paměť se obvykle komprimuje.

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

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

  • Systém má malou fyzickou paměť. Zjistí se buď oznámení o nedostatku paměti z operačního systému, nebo nedostatku paměti, jak je uvedeno hostitelem.

  • Paměť, která se používá přidělenými 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

Jakmile modul CLR inicializuje 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 na rozdíl od nativní haldy v operačním systému.

Pro každý spravovaný proces existuje spravovaná halda. Všechna vlákna v procesu přidělují paměť objektům 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í všech 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 nebo na ní záviset, ani by se neměla pokoušet nakonfigurovat velikost paměti dostupné pro přidělení segmentů.

Čím méně objektů přidělených haldě, tím méně práce musí uvolňování paměti udělat. 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 pouze 15 bajtů.

Když se aktivuje uvolňování paměti, uvolňování paměti uvolní paměť obsazenou mrtvými objekty. Uvolněním procesu se zkomprimují živé objekty tak, aby byly přesunuty dohromady, a mrtvé místo se odebere, čímž se halda zmenší. Tím se zajistí, že objekty přidělené společně zůstanou na spravované haldě, aby se zachovala jejich lokalita.

Dotěrnost (frekvence a doba trvání) uvolňování paměti je výsledkem objemu přidělení a množství přeživlé paměti ve spravované haldě.

Haldu lze považovat za akumulační dvě haldy: 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 extrémně velký.

Tip

Můžete nakonfigurovat prahovou hodnotu pro objekty tak, aby přešly na haldu velkého objektu.

Generace

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

  • Je rychlejší zkomprimovat 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 mají k nim přístup aplikace přibližně ve stejnou dobu.

Uvolňování paměti se primárně vyskytuje s rekultivací 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 zpracovat 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 života aplikace, které přetrvají kolekce, se podporují a ukládají v generacích 1 a 2. Vzhledem k tomu, že je rychlejší komprimovat část spravované haldy než celá halda, toto schéma umožňuje uvolňování paměti v konkrétní generaci místo uvolnění paměti pro celou spravovanou haldu pokaždé, když provede kolekci.

  • Generace 0. Toto je nejmenší generace 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ě se vytvářejí 0 kolekcí. Pokud jsou ale velké objekty, přejdou 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šina objektů se uvolní pro uvolňování paměti v generaci 0 a nepřežije do další generace.

    Pokud se aplikace pokusí vytvořit nový objekt, když je generace 0 plná, uvolňování paměti provede kolekci při pokusu o uvolnění adresního prostoru objektu. Uvolňování paměti začíná prozkoumáním objektů v generaci 0 místo všech objektů ve spravované haldě. Samotná kolekce generace 0 často uvolní dostatek paměti, aby mohla aplikace 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 uvolňování paměti provede kolekci generace 0, zkomprimuje paměť pro dosažitelné objekty a podporuje je do generace 1. Vzhledem k tomu, že objekty, které přetrvají kolekce, mají tendenci mít delší životnost, dává smysl je propagovat na vyšší generaci. Odpadkový kolektor nemusí znovu prověřovat objekty v generacích 1 a 2 pokaždé, když provádí kolekci generace 0.

    Pokud kolekce generace 0 nevyvolá dostatek paměti pro aplikaci k vytvoření nového objektu, může uvolňování paměti provést kolekci generace 1, pak generace 2. Objekty v generaci 1, které přetrvají 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 živá po dobu trvání procesu.

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

    Objekty na 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 jejích 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 povýšení

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

  • Objekty, které přetrvají uvolňování paměti generace 0, jsou povýšeny na generaci 1.
  • Objekty, které přetrvají uvolňování paměti generace 1, jsou povýšeny na generaci 2.
  • Objekty, které přetrvají uvolňování paměti generace 2, zůstávají v generaci 2.

Když uvolňování paměti zjistí, že míra přežití je vysoká v generaci, zvyšuje 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: nenechá funkční sadu aplikace příliš velkou tím, že zpozdí uvolňování paměti a nenechá uvolňování paměti příliš často.

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 ukazuje výchozí velikosti dočasného segmentu.

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

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

Velikost uvolněné paměti z dočasného uvolňování paměti je omezena na velikost dočasného segmentu. Velikost paměti, která je uvolněna, je úměrná prostoru, který byl obsazen 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ů.

  • Fáze přemí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í velká halda objektu (LOH) komprimovaná, protože kopírování velkých objektů ukládá trest za výkon. V .NET Core a v rozhraní .NET Framework 4.5.1 a novější však můžete vlastnost použít GCSettings.LargeObjectHeapCompactionMode ke komprimci velké haldy 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é:

  • Stohové kořeny. Stack variables provided by the just-in-time kompilátor (JIT) and stack walker. Optimalizace JIT můžou prodloužit nebo zkrátit oblasti kódu, ve kterých se proměnné zásobníku hlásí do uvolňování paměti.

  • Úchyty pro uvolňování paměti Zpracovává tento bod 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é by mohly 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é aktivovaly 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.

When a thread triggers a Garbage Collection

Nespravované prostředky

U většiny objektů, které vaše aplikace vytvoří, můžete spoléhat na uvolňování paměti a automaticky provádět potřebné úlohy správy 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í. I když uvolňování paměti dokáže sledovat životnost spravovaného objektu, který zapouzdřuje nespravovaný prostředek, nemá specifické znalosti o tom, jak prostředek vyčistit.

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žijete objekt, který zapouzdřuje nespravovaný prostředek, nezapomeňte volat Dispose podle potřeby.

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

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

Viz také