Správa paměti představuje soubor principů a mechanismů, které zajišťují efektivní přidělování a uvolňování operační paměti během běhu programu.

Halda a vznik objektů

Halda (heap) je oblast dynamické paměti, z níž program získává paměť pro objekty, jejichž velikost nebo životnost není předem známa při kompilaci. Objekty vytvořené na haldě přetrvávají tak dlouho, dokud k nim existuje přístup (reference).

Příklad vytvoření objektu na haldě v jazyce C#:

var osoba = new Osoba();

V tomto příkladu je instance třídy Osoba alokována na haldě.

Nutnost uvolňování objektů

Paměť na haldě není neomezená. Pokud program po sobě neuvolní již nepotřebné objekty, dojde časem k vyčerpání dostupné paměti. To může vyvolat výjimku OutOfMemoryException, způsobit pád aplikace nebo výrazné zpomalení systému. Správné uvolňování paměti je proto nezbytné pro dlouhodobý běh aplikací.

Způsoby čištění haldy

Existují dva hlavní přístupy, jak uvolňovat objekty z haldy:

  • Manuální
  • Automatické

Manuální uvolňování objektů

V některých jazycích (například C) musí programátor uvolnit paměť explicitně. Paměť se alokuje funkcí jako malloc a uvolňuje funkcí free.

char *data = malloc(100);
if (data != NULL) {
    // vyčištění paměti
    free(data);
}

Výhody:

  • Plná kontrola nad alokací a uvolněním.

Nevýhody:

Double free

Double free je chyba při běhu programu, která je vyhozena při opakovaném pokusu o uvolnění stejné paměti.

Automatizované uvolňování

Existují různé způsoby pro automatickou správu paměti. Jedná se o:

  • Garbage Collection
    • Reference Counting - Například v C++. Každý objekt si uchovává počet odkazů, které na něj vedou. Pokud počet klesne na nulu, objekt je okamžitě uvolněn.
    • Generační Garbage Collection - Například v C# nebo Javě. Vychází z pozorování, že většina objektů žije krátce. Paměť je rozdělena na “mladou” a “starou” generaci. Krátkodobé objekty se rychle uvolní, dlouhodobé se přesunou do starší generace. Tento přístup zvyšuje efektivitu.
  • Ownership - Například v Rust. Správa paměti se řeší pomocí systému vlastnictví a zápůjček. Každý objekt má přesně jednoho vlastníka, který zodpovídá za jeho životnost.

Garbage Collector

Moderní jazyky jako C# nebo Java používají Garbage Collector (GC), který automaticky vyhledává objekty, ke kterým již neexistuje žádná reference, a uvolňuje jejich paměť.

Výhody:

  • Nižší riziko chyb při uvolňování paměti.
  • Snazší programování.

Nevýhody:

  • Nelze plně předvídat okamžik uvolnění paměti.
  • Vyšší režie pro správu paměti

Fungování Garbage Collectoru

Garbage Collector pracuje v několika krocích:

  1. Identifikace živých objektů - GC začne od tzv. rootů (například proměnné na zásobníku, statické proměnné, CPU registry) a sleduje všechny odkazy, aby zjistil, které objekty jsou stále dosažitelné.
  2. Označení nepoužívaných objektů - Všechny objekty, ke kterým není možné dojít sledováním referencí, jsou označeny jako kandidáti ke smazání.
  3. Uvolnění paměti - Nepotřebné objekty jsou odstraněny a jejich paměť je uvolněna.
  4. Kompakce paměti (některé implementace) - Pokud se paměť výrazně fragmentuje, GC přesune objekty tak, aby vznikl souvislý blok volné paměti, čímž se urychlí budoucí alokace.

Generační Garbage Collector

.NET využívá generační Garbage Collector, který objekty rozděluje do generací:

  • Generace 0: Krátce žijící objekty (například lokální proměnné v metodách).
  • Generace 1: Objekty přeživší alespoň jednu kolekci (sběr).
  • Generace 2: Dlouho žijící objekty (například globální cache, velké datové struktury).

Důvodem je efektivita. Statistiky ukazují, že většina objektů zanikne krátce po svém vzniku, takže časté čištění pouze generace 0 je rychlé a šetří zdroje.

Memory leak

Memory leak (únik paměti) vzniká, pokud objekt zůstává v paměti, i když už ho program nepotřebuje. V prostředí s GC tento stav nastává, pokud na objekt stále existuje reference, která brání jeho uvolnění, ačkoliv je objekt fakticky „mrtvý“ z pohledu logiky aplikace.

GC dokáže uvolnit jen objekty, ke kterým není možné dojít. Pokud tedy existuje nepotřebná reference (například objekt uložený v kolekci, která není nikdy vyprázdněna), GC únik paměti neodhalí. To může vést k:

  • rostoucí spotřebě paměti,
  • degradaci výkonu,
  • pádům aplikace při vyčerpání paměti.

Prostředí .NET díky Garbage Collectoru značně omezuje riziko klasických memory leaků známých z jazyků s manuální správou paměti. Přesto však úniky paměti v .NET nejsou zcela vyloučené.

Například pokud zůstanou objekty zaregistrovány v událostech (event handlers), Garbage Collector je nemůže uvolnit, protože na ně stále existuje odkaz. Podobně mohou způsobit únik paměti statické kolekce, které uchovávají reference na objekty, jež již nejsou potřeba. Dalším častým příkladem jsou cachovací mechanismy bez limitů nebo bez periodického čištění, které postupně spotřebují veškerou dostupnou paměť.

Ukázka Memory leaku

Příklad úniku paměti pomocí event handleru v C#:

public class Publisher
{
    public event EventHandler SomethingHappened;
}
 
public class Subscriber
{
    public void Subscribe(Publisher pub)
    {
        pub.SomethingHappened += (s, e) => Console.WriteLine("Event handled.");
    }
}

Pokud objekt Subscriber nebude nikdy odregistrován od události SomethingHappened, zůstane v paměti i po skončení své logické životnosti, protože Publisher na něj drží referenci prostřednictvím event handleru.

Správným řešením je odregistrace:

pub.SomethingHappened -= myHandler;