Sběr odpadu

Správa paměti v JavaScriptu se provádí automaticky a pro nás neviditelně. Vytváříme primitiva, objekty, funkce... Vše, co vyžaduje paměť.

Co se stane, když už něco není potřeba? Jak to JavaScript engine objeví a vyčistí?

Dosažitelnost

Hlavním konceptem správy paměti v JavaScriptu je dosažitelnost .

Jednoduše řečeno, „dosažitelné“ hodnoty jsou ty, které jsou přístupné nebo nějak použitelné. Je zaručeno, že budou uloženy v paměti.

  1. Existuje základní sada inherentně dosažitelných hodnot, které nelze ze zřejmých důvodů smazat.

    Například:

    • Aktuálně prováděná funkce, její lokální proměnné a parametry.
    • Další funkce pro aktuální řetězec vnořených volání, jejich lokální proměnné a parametry.
    • Globální proměnné.
    • (existují i ​​další, interní)

    Tyto hodnoty se nazývají kořeny .

  2. Jakákoli jiná hodnota je považována za dosažitelnou, pokud je dosažitelná z kořene pomocí odkazu nebo řetězce odkazů.

    Pokud je například objekt v globální proměnné a tento objekt má vlastnost odkazující na jiný objekt, že objekt je považován za dosažitelný. A ty, na které odkazuje, jsou také dosažitelné. Následujte podrobné příklady.

V enginu JavaScriptu existuje proces na pozadí, který se nazývá garbage collector. Sleduje všechny objekty a odstraňuje ty, které se staly nedostupnými.

Jednoduchý příklad

Zde je nejjednodušší příklad:

// user has a reference to the object
let user = {
 name: "John"
};

Zde šipka znázorňuje odkaz na objekt. Globální proměnná "user" odkazuje na objekt {name: "John"} (pro stručnost tomu budeme říkat John). "name" Johnova vlastnost ukládá primitivum, takže je namalováno uvnitř objektu.

Pokud je hodnota user je přepsán, odkaz se ztratí:

user = null;

Nyní se John stává nedosažitelným. Neexistuje žádný způsob, jak se k němu dostat, žádné odkazy na něj. Garbage collector vyřadí data a uvolní paměť.

Dvě reference

Nyní si představme, že jsme zkopírovali referenci z user na admin :

// user has a reference to the object
let user = {
 name: "John"
};

let admin = user;

Nyní, když uděláme totéž:

user = null;

…Pak je objekt stále dosažitelný přes admin globální proměnná, takže musí zůstat v paměti. Pokud přepíšeme admin také jej lze odstranit.

Propojené objekty

Nyní složitější příklad. Rodina:

function marry(man, woman) {
 woman.husband = man;
 man.wife = woman;

 return {
 father: man,
 mother: woman
 }
}

let family = marry({
 name: "John"
}, {
 name: "Ann"
});

Funkce marry „vezme“ dva objekty tím, že jim na sebe dá odkazy a vrátí nový objekt, který je obsahuje oba.

Výsledná struktura paměti:

Od této chvíle jsou všechny objekty dosažitelné.

Nyní odstraníme dva odkazy:

delete family.father;
delete family.mother.husband;

Nestačí odstranit pouze jednu z těchto dvou referencí, protože všechny objekty by byly stále dosažitelné.

Ale pokud odstraníme oba, uvidíme, že John už nemá žádnou příchozí referenci:

Odchozí reference nehrají roli. Pouze příchozí mohou učinit objekt dosažitelným. Takže John je nyní nedostupný a bude odstraněn z paměti se všemi svými daty, která se také stala nepřístupnou.

Po sběru odpadu:

Nedosažitelný ostrov

Je možné, že se celý ostrov vzájemně propojených objektů stane nedosažitelným a bude odstraněn z paměti.

Zdrojový objekt je stejný jako výše. Potom:

family = null;

Obrázek v paměti se změní na:

Tento příklad ukazuje, jak důležitý je koncept dosažitelnosti.

Je zřejmé, že John a Ann jsou stále propojeni, oba mají příchozí reference. Ale to nestačí.

Původní "family" objekt byl odpojen od kořenového adresáře, již na něj není žádný odkaz, takže celý ostrov se stane nedostupným a bude odstraněn.

Interní algoritmy

Základní algoritmus garbage collection se nazývá „mark-and-sweep“.

Pravidelně se provádějí následující kroky „sběru odpadu“:

  • Sběrač odpadků zakoření a „označí“ (pamatuje si) je.
  • Pak navštíví a „označí“ všechny jejich reference.
  • Poté navštíví označené objekty a označí jejich Reference. Všechny navštívené objekty jsou zapamatovány, aby se stejný objekt v budoucnu nenavštívil dvakrát.
  • …A tak dále, dokud nejsou navštíveny všechny dostupné (od kořenů) reference.
  • Všechny objekty kromě označených budou odstraněny.

Nechť naše objektová struktura vypadá například takto:

Na pravé straně jasně vidíme „nedosažitelný ostrov“. Nyní se podívejme, jak se s tím vypořádá sběrač odpadků „mark-and-sweep“.

První krok označuje kořeny:

Poté sledujeme jejich reference a označíme odkazované objekty:

…A pokud je to možné, pokračujte ve sledování dalších odkazů:

Nyní jsou objekty, které nebylo možné v tomto procesu navštívit, považovány za nedosažitelné a budou odstraněny:

Proces si můžeme představit i jako vylití obrovského kbelíku barvy od kořenů, které proteče všemi odkazy a označí všechny dosažitelné předměty. Neoznačené jsou pak odstraněny.

To je koncept, jak funguje sběr odpadu. JavaScriptové motory používají mnoho optimalizací, aby běžel rychleji a nezaváděl žádné zpoždění při provádění kódu.

Některé z optimalizací:

  • Generační sbírka – objekty jsou rozděleny do dvou sad:„nové“ a „staré“. V typickém kódu má mnoho objektů krátkou životnost:objevují se, dělají svou práci a rychle umírají, takže má smysl sledovat nové objekty a vymazat z nich paměť, pokud tomu tak je. Ty, které přežijí dostatečně dlouho, „stárnou“ a jsou méně často vyšetřovány.
  • Přírůstkové shromažďování – pokud je objektů mnoho a snažíme se projít a označit celou sadu objektů najednou, může to chvíli trvat a způsobit viditelné zpoždění při provádění. Takže engine rozdělí celou sadu existujících objektů na více částí. A pak tyto části vyčistěte jednu po druhé. Existuje mnoho malých sbírek odpadu namísto úplného. To vyžaduje určité další vedení účetnictví mezi nimi, aby bylo možné sledovat změny, ale místo velkého zpoždění dochází k mnoha malým zpožděním.
  • Shromažďování v době nečinnosti – garbage collector se pokouší spustit pouze v době, kdy je CPU nečinný, aby se snížil možný vliv na provádění.

Existují další optimalizace a varianty algoritmů pro sběr odpadu. Jakkoli bych je zde chtěl popsat, musím se zdržet, protože různé motory implementují různé vychytávky a techniky. A co je ještě důležitější, věci se mění s vývojem motorů, takže hlubší studium „v předstihu“ bez skutečné potřeby se pravděpodobně nevyplatí. Pokud se samozřejmě nejedná o čistě zájmovou záležitost, níže pro vás budou některé odkazy.

Shrnutí

Hlavní věci, které byste měli vědět:

  • Sběr odpadu se provádí automaticky. Nemůžeme to vynutit ani tomu zabránit.
  • Objekty jsou uchovány v paměti, dokud jsou dostupné.
  • Být odkazem není totéž jako být dosažitelný (z kořene):sada vzájemně propojených objektů se může stát nedosažitelným jako celek, jak jsme viděli v příkladu výše.

Moderní motory implementují pokročilé algoritmy pro sběr odpadu.

Obecná kniha „The Garbage Collection Handbook:The Art of Automatic Memory Management“ (R. Jones et al) pokrývá některé z nich.

Pokud jste obeznámeni s nízkoúrovňovým programováním, podrobnější informace o garbage collectoru V8 najdete v článku Prohlídka V8:Garbage Collection.

Blog V8 také čas od času publikuje články o změnách ve správě paměti. Chcete-li se přirozeně dozvědět více o garbage collection, měli byste se lépe připravit tím, že se seznámíte s vnitřními prvky V8 obecně a přečtěte si blog Vjačeslava Egorova, který pracoval jako jeden z inženýrů V8. Říkám:„V8“, protože to nejlépe pokrývají články na internetu. U jiných motorů je mnoho přístupů podobných, ale sběr odpadu se v mnoha aspektech liší.

Hluboká znalost motorů je dobrá, když potřebujete nízkoúrovňové optimalizace. Bylo by moudré naplánovat si to jako další krok poté, co se s daným jazykem seznámíte.