Začínáme s jazykem C#

Jazyk C# se představuje (2. díl)

Časová náročnost (min):

Začátečník

Pokročilý

Profesionál

Použitý operační systém : Hlavní vývojový nástroj :

Další vývojový software :

Jiný software :

Windows 2000 SP3

Visual C# .NET 2002

Žádný

Žádný

 

 

Vážení čtenáři,

 

po minulé úvodní části do světa jazyka C# a platformy .NET Framework již poznáte základy, na nichž je veškerá funkcionalita vize .NET položena. V dnešním díle si přiblížíme způsob práce automatické správy paměti, kterou vykonává Garbage Collection.

 

Obsah

 Garbage Collection na první pohled

 Garbage Collection pod drobnohledem

 Generace 0 a Garbage Collection

 Generace 1 a Garbage Collection

 Generace 2 a Garbage Collection

 Význam Garbage Collection pro programátory

 

 

Garbage Collection na první pohled

 

Garbage Collection (dále též jako GC) je nízkoúrovňová softwarová služba, která zabezpečuje automatickou správu paměti. Hned na začátku si ovšem musíme říci, že Garbage Collection nehospodaří s celou pamětí počítače, ale jenom s jednou vyhrazenou oblastí, která je označována jako řízená hromada (managed heap). Řízenou hromadu jsme si charakterizovali již v minulé části seriálu, jenom jsme ji ještě neobdařili přídomkem „řízená“. Řízená hromada slouží jako globální úložiště objektů, neboli instancí řízených tříd. Když programátor vytvoří instanci libovolné třídy z bázové knihovny třídy .NET Frameworku (.NET Framework Class Library), tato instance je uložena na řízené hromadě. Instance tříd, neboli objekty, které jsou automaticky umísťované do řízené hromady, jsou často označované jako řízené instance, nebo také řízené objekty. Na každou řízenou instanci směruje vždy přinejmenším jeden odkaz, jenž je uložen v inicializační referenční proměnné, která byla použita při vytváření instance. Kromě tohoto odkazu však můžou na instanci odkazovat také další referenční proměnné. Automatická správa paměti pomocí Garbage Collection je založena na vytváření a kontrole tzv. stromu odkazů. Strom odkazů představuje stromovou strukturu všech odkazů (včetně vnořených), které jsou nasměrované na jeden objekt řízené hromady. Jelikož GC vytváří strom odkazů pro každý objekt, v každém okamžiku ví přesně určit, kolik ukazatelů, resp. odkazů referenčních proměnných je spojeno s konkrétním objektem. Abyste tuto situaci lépe pochopili, pomozme si grafickou ukázkou.

 

Předpokládejme, že na řízené hromadě se nacházejí dva objekty, přičemž v zásobníku jsou uloženy čtyři referenční proměnné. Dvě z těchto referenčních proměnných obsahují odkazy na jeden z objektů a zbývající dvě proměnné jsou zase nasměrovány na druhý z objektů. Grafickou podobu nastíněné situace můžete vidět na obr. 1.

 

 

Obr. 1 – Vztah referenčních proměnných a objektů uložených na řízené hromadě

 

Jak si můžete všimnout, referenční proměnné č. 1 a 3 obsahují odkazy na Objekt 1, zatímco referenční proměnné č. 2 a 4 ukazují na Objekt 2. Toto je zcela běžná situace, v rámci které může s jedním objektem pracovat větší počet odkazových proměnných. Garbage Collection má pod kontrolou celý životní cyklus objektů, a proto ví, že na každý námi vytvořený objekt na řízené hromadě odkazují dvě referenční proměnné. Strom odkazů je v tomto případě jednoduchý, ovšem s vzrůstajícím počtem referenčních proměnných (které odkazují na příslušný objekt), se může bohatě rozrůstat a značně komplikovat. Naštěstí, algoritmus práce Garbage Collection je spolehlivý a dovede si poradit také s některými náročnějšími prvky, mezi které můžeme zařadit například vytváření vnořených odkazů na vnitřní objekty.

 

Pokračujme ovšem v našem iluzorním příkladu a prostudujme, co by stalo, kdyby zanikli dvě referenční proměnné (č. 3 a 4). Grafickou ukázku zprostředkovává obr. 2.

 

 

Obr. 2 – Likvidace dvou referenčních proměnných

 

Odkazová proměnná zanikne vždy, když se dostane mimo svůj obor platnosti. Je-li například oborem referenční proměnné funkce, bude proměnná zlikvidována vždy, když bude proveden programový kód funkce a funkce vrátí řízení volající proceduře. Když dojde k této situaci, nastane likvidace referenční proměnné – proměnná bude jednoduše odstraněna ze zásobníku a její hodnota bude zlikvidována. Protože hodnotou referenční proměnné je odkaz, neboli ukazatel, bude podroben destrukci právě tento. To znamená, že od této chvíle bude na každý objekt ukazovat jenom jedna referenční proměnná (na Objekt 1 proměnná č. 1 a na Objekt 2 proměnná č. 2). Tuto změnu samozřejmě zaregistruje také Garbage Collection, která vhodným způsobem upraví strom odkazů tak, aby odrážel reálnou situaci. GC tak zjistí, že oba objekty jsou stále využívané, protože na ně pořád směrují jisté referenční proměnné. Co se ovšem stane, když dojde k likvidaci i těchto zbývajících odkazových proměnných? Garbage Collection opět upraví strom odkazů, nicméně v tomto případě zjistí, že ani na jeden z objektů neodkazují žádné referenční proměnné. Když na objekt nesměrují žádné odkazové proměnné, znamená to, že objekt již není zapotřebí, a proto jej Garbage Collection z paměti uvolní (obr. 3).  

 

 

Obr. 3 – Likvidace zbývajících referenčních proměnných a destrukce nepotřebných objektů

 

Výsledkem je tedy nejenom odstranění referenčních proměnných ze zásobníku, ale také likvidace nepotřebných objektů z řízené hromady. Jak ovšem Garbage Collection zjistí, že objekt je nepotřebný a že je možné jej odstranit? V uvedeném příkladu jsme si ukázali, že k destrukci objektu dojde v okamžiku, kdy neexistují žádné referenční proměnné, které by uchovávaly odkazy na tento objekt. GC tuto skutečnost zjistí velice jednoduše podle podoby stromu odkazů. Když počet odkazů na objekt klesne na nulovou hodnotou, stává se objekt soustem pro Garbage Collection.

 

Ještě před samotnou likvidací objektu je však proveden kód, který se nachází ve speciální metodě objektu s názvem destruktor (destruktor v C# má podobnou syntaxi jako jeho kolega z Managed Extensions for C++). Pomocí destruktoru může objekt provést jisté „čistící“ práce, jako je například uvolnění paměťových zdrojů, které byly alokovány neřízenými prostředky, či jiné potřebné činnosti). Destruktor objektu implicitně volá metodu Finalize bázové třídy objektu.  

  

 

Garbage Collection pod drobnohledem

 

Řízená hromada je souvislý úsek paměti, který je určen pro ukládání řízených objektů. Pokud bychom se na řízenou hromadu podívali blíže, zjistili bychom, že objekty jsou zde skladované v několika generacích. Pojem generace v tomto kontextu představuje jistou ohraničenou oblasti paměti uvnitř řízené hromady. Garbage Collection rozeznává tří generace: 0, 1 a 2. Rozdělení celé řízené hromady na menší části, tedy generace, je velmi výhodné, protože Garbage Collection nemusí pracovat s celou řízenou hromadou, ale jenom s odpovídající generací, případně generacemi. Rozdělení objektů do jednotlivých generací se uskutečňuje v závislosti od životních cyklů těchto objektů. V generaci 0 se nacházejí objekty, které byly vytvořené teprve nedávno a u kterých je rovněž předpoklad, že by jejich „život“ nemusel trvat příliš dlouho. Nultá generace je využívána nejčastěji, protože většina aplikací využívá množství svých objektů jenom po určitou, ovšem ne dlouhou, dobu. Pokud je životní cyklus objektu delší, je možné, že se dostane do generace 1. Generace 1 seskupuje objekty, pro které je charakteristická střední doba životnosti. Konečně, do poslední generace (s pořadovým číslem 2), jsou ukládány dlouho žijící objekty, které jsou v jednotlivých etapách svého životního cyklu aktivně a frekventovaně využívány. Je rovněž důležité vědět, že kapacitní náročnost jednotlivých generací je variabilní. To znamená, že generace mohou uchovávat objekty jenom po určitou kapacitní hranici. V případě nulté generace se tato hranice blíží 256 KB, u druhé generace je to přibližně 2 MB a třetí generace je pak schopna pojmou objekty až do maximální deseti megabajtové hranice.  

 

 

Generace 0 a Garbage Collection

 

Práci Garbage Collection si budeme demonstrovat ještě jednou, a to podrobněji a s ohledem na využití generací objektů. Nejprve si však ukažme skutečnou, nikoliv idealizovanou, podobu řízené hromady s nultou generací objektů (obr. 4).

 

 

Obr. 4 – Skutečná podoba řízené hromady s nultou generací objektů

 

Na tomto obrázku můžete vidět podobu řízené hromady s nultou generací objektů. Generaci 0 tvoří tři objekty, u nichž můžeme předpokládat, že byly vytvořeny teprve nedávno (náš předpoklad vychází z teze, podle které jsou do generace 0 ukládány „nejmladší“ objekty). Za posledním objektem nulté generace je umístěn speciální ukazatel, jenž ukazuje na paměťovou adresu, na kterou bude uložen další vytvořený objekt.

 

Na všechny tři objekty jsou nasměrovány odkazy přinejmenším tří referenčních proměnných, které jsou uložené na zásobníku. Tato skutečnost ovšem není v obrázku znázorněna.    

  

Jestliže bude do generace 0 uložen další objekt, bude situace následovní (obr. 5).

 

 

Obr. 5 – Přidání dalšího objektu do nulté generace

 

Aby mohl být do nulté generace přidán další objekt, budou uskutečněné následující operace:

 

  1. Garbage Collection zjistí, zdali je v generaci 0 dostatečné místo pro uložení nového objektu (připomeňme, že velikost nulté generace je přibližně 256 KB). Jestliže je prostor dostatečný, bude nový objekt uložen do generace 0 řízené hromady.
  2. Garbage Collection automaticky aktualizuje ukazatel, jenž obsahuje paměťovou adresu, na které bude umístěn další vytvořený objekt. Nyní tedy ukazatel ukazuje na paměťový prostor za nově přidaným objektem.
  3. Alokovaný prostor generace 0 se zvětší o hodnotu, která odpovídá kapacitní náročnosti nově přidaného objektu.

 

V této souvislosti by vás mohla napadnout následující otázka: „A co se stane, když bude překročen paměťový prostor nulté generace?“. V tomto případě Garbage Collection provede několik činností:

 

  1. Jelikož není v generaci 0 dostatek paměťového prostoru pro uložení dalšího objektu, GC provede zjištění, zdali se v nulté generaci nenacházejí objekty, které by mohly být uvolněny. Nepotřebné objekty jsou objekty, na které nesměrují žádné odkazy referenčních proměnných. Najdou-li se v generaci 0 objekty tohoto typu, budou odstraněny. Takto získané paměťové místo bude zpřístupněno pro další nově vytvořené objekty.
  2. Garbage Collection dále realizuje proces přemístění objektů, a to tak, aby byl vytvořen souvislý celek objektů. Důvodem pro vytvoření souvislého celku objektů v nulté generaci je skutečnost, že k takto seskupeným objektům se rychleji přistupuje.

 

Je to podobné jako při defragmentaci pevného disku. V rámci defragmentace se spájejí fragmenty souborů proto, aby pevný disk mohl k souboru přistupovat s co možná nejvyšší rychlostí. Garbage Collection provádí něco podobného, ovšem neoperuje s daty souborů, nýbrž s objekty řízené hromady. 

 

  1. Ihned po přemístění objektů je nezbytně nutné také aktualizovat ukazatele, které ukazovaly na objekty ještě předtím, než byl nastartován proces přemísťování objektů. Garbage Collection všem přemístěným objektům přiřadí nové paměťové adresy, což znamená, že i poté, co budou objekty přeložené na jiné místo, budou nadále dostupné pomocí příslušných referenčních proměnných.
  2. Do generace 0 budou přidány nové objekty.

 

Celý proces práce Garbage Collection můžete vidět na obr. 6.

 

 

Obr. 6 – Generace 0 a Garbage Collection

 

 

Generace 1 a Garbage Collection

 

Jak jste viděli, pokud Garbage Collection nalezne v nulté generaci nepotřebné objekty, postará se o jejich likvidaci. V naší ukázce jsme přímo počítali s touto možností, ovšem ve skutečnosti se může také stát, že ačkoliv bude generace 0 plná, nebudou při prvním průchodu Garbage Collection nalezeny žádné nepotřebné objekty. Garbage Collection ví, že v tomto případě nemůže odstranit žádný objekt, protože všechny objekty jsou pořád aktivní. Pokud tedy přijde požadavek na vytvoření nového objektu, GC přemístí některé objekty do generace 1. Objekty, které zůstanou v generaci 0 jsou přemístěné, ukazatele na tyto objekty jsou aktualizované a je vytvořen souvislý blok objektů (rovněž dochází k aktualizaci speciálního ukazatele). Garbage Collection také aktualizuje ukazatele na objekty v generaci 1. Objekty, které byly přemístěné do generace 1 jsou stále živé a schopné vykonávat jakoukoliv činnost. Jednoduše řečeno, přesun objektů mezi generacemi nijak neovlivní práci těchto objektů. Poté, co Garbage Collection provede vše, co jsme si uvedli, přidá do generace 0 nové objekty. Potenciální podobu řízené hromady po vytvoření generace 1 zobrazuje obr. 7.

 

 

Obr. 7 – Generace 1 a Garbage Collection

 

Na obrázku jsou zřetelně zobrazené obě generace objektů. Všimněte si pozici speciálního ukazatele, jenž je nasměrován na paměťovou adresu, na níž bude uložen nový objekt. Jak můžete vidět, ukazatel se nachází v generaci 0 a ne v generaci 1. Na první pohled to možná vypadá poněkud divně, ovšem vše je v naprostém pořádku. Speciální ukazatel je na tomto místě proto, že jakýkoliv nový objekt bude automaticky uložen do nulté generace. Když bude v generaci 0 dostatek prostoru pro nový objekt, bude objekt okamžitě uložen na místo, které je vyznačeno speciálním ukazatelem. V opačném případě bude spuštěn proces „sběru odpadků“, při kterém dojde k odstranění nepotřebných objektů, případně také k přesunu objektů s delší životností do generace 1.

 

 

Generace 2 a Garbage Collection

 

Automatická správa paměti podporuje tři generace objektů. Ano, jak byste mohli čekat, poslední je generace s pořadovým číslem 2. Do této generace Garbage Collection umísťuje dlouho žijící objekty, které ačkoliv nejsou využívané často, jsou zapotřebí k správnému chodu aplikací. Objekty jsou do generace 2 přesouvané z generace 1 (nikdy ne z generace 0). Pokud je generace 1 plná a přijde požadavek na přesun objektů z generace 0 do generace 1, Garbage Collection uskuteční průzkum generace 1 a zjistí, zdali se v této generaci nenacházejí nepotřebné objekty. Pokud se najdou objekty, které již nejsou zapotřebí, GC nařídí jejich likvidaci. Jestliže je ovšem generace 1 plná, budou jisté objekty přenesené do generace 2, čímž se v generaci 1 uvolní požadovaný prostor (tento prostor bude následně vyplněn objekty, které přicházejí z generace 0). Při všech operacích dochází samozřejmě k již zmíněným činnostem, jako je kompaktní uložení objektů do souvislého bloku, aktualizace ukazatelů na tyto objekty a další. Generace 2 je schopna objektům nabídnout úložiště o velikosti přibližně 10 MB. Pokud se systém dostane do extrémní situace a nastane zaplnění generace 2, Garbage Collection vykoná průzkum u všech generací (2, 1 a 0) a pokusí se odstranit nepotřebné objekty a uvolnit místo na řízené hromadě. Objekty, které se nacházejí v generaci 2 již ovšem nejsou nikam přemísťované, protože (prozatím) neexistuje žádná další generace objektů. 

 

 

Význam Garbage Collection pro programátory

 

V závěrečné části prozkoumáme přínosy, které přináší Garbage Collection do života nás, programátorů. Automatická správa paměti vykonává přesně to, co je obsaženo v jejím názvu, tedy automaticky kontroluje a v případě potřeby také odstraňuje objekty z řízené hromady. Pokud budete pracovat s objekty (a věřte, že s nimi se v prostředí .NET Frameworku střetnete doslova na každém kroku), vaším úkolem bude pouze tvorba těchto objektů. Nemusíte se tedy zajímat o explicitní odstraňování objektů z paměti počítače – to za vás provede Garbage Collection. Jak jsme si v této části našeho seriálu ukázali, automatická správa paměti je dost chytrá na to, aby tento úkol splnila bez jakýchkoliv větších potíží. Na druhou stranu, Garbage Collection pracuje s nedeterministickou finalizací, což znamená, že programátoři si nemohou být jisti, v jakém časovém okamžiku dojde k likvidaci jejich objektů. Jestliže jste dříve programovali například v jazyku C++, víte, že zde byla situace jiná: Správa objektů byla zcela ve vašich rukou, ovšem pokud jste použili operátor delete na instanci třídy, mohli jste si být jisti, že požadovaná instance byla ihned zlikvidována. I když o tuto jistotu v prostředí platformy .NET Framework přicházíte, nemusíte být zklamání. Garbage Collection pracuje opravdu spolehlivě a ještě k tomu zabraňuje vzniku poměrně častých programátorských chyb (např. příliš brzké odstranění objektu, nebo pokud o opětovné odstranění již zlikvidovaného objektu). Práce Garbage Collection s jednotlivých objektů se dá v jistým smyslu ovlivnit, ovšem tuto problematiku si přiblížíme až někdy příště.

 

 

Ján Hanák