Úvod do ZGC: Škálovateľný a experimentálny zberač odpadov JVM s nízkou latenciou

1. Úvod

Dnes nie je nezvyčajné, že aplikácie obsluhujú súčasne tisíce alebo dokonca milióny používateľov. Takéto aplikácie potrebujú obrovské množstvo pamäte. Správa celej tejto pamäte však môže ľahko ovplyvniť výkon aplikácií.

Aby sa tento problém vyriešil, Java 11 predstavila Z Garbage Collector (ZGC) ako experimentálnu implementáciu garbage collector (GC).

V tomto návode uvidíme ako sa spoločnosti ZGC darí udržiavať nízke doby pauzy aj na viac terabajtových hromadách.

2. Hlavné pojmy

Aby sme pochopili, ako ZGC funguje, musíme pochopiť základné pojmy a terminológiu, ktorá stojí za správou pamäte a zberačmi odpadu.

2.1. Správa pamäte

Fyzická pamäť je RAM, ktorú poskytuje náš hardvér.

Operačný systém (OS) prideľuje priestor pre virtuálnu pamäť pre každú aplikáciu.

Samozrejme, ukladáme virtuálnu pamäť do fyzickej pamäte a OS je zodpovedný za udržiavanie mapovania medzi nimi. Toto mapovanie zvyčajne zahŕňa hardvérovú akceleráciu.

2.2. Multi-mapovanie

Multi-mapping znamená, že vo virtuálnej pamäti sú špecifické adresy, ktoré smerujú na rovnakú adresu vo fyzickej pamäti. Keďže aplikácie pristupujú k údajom prostredníctvom virtuálnej pamäte, o tomto mechanizme nič nevedia (a ani to nemusia).

Účinne mapujeme viac rozsahov virtuálnej pamäte na rovnaký rozsah vo fyzickej pamäti:

Na prvý pohľad nie sú zrejmé prípady jeho použitia, ale uvidíme neskôr, že spoločnosť ZGC to potrebuje na svoje kúzlo. Poskytuje tiež určité zabezpečenie, pretože oddeľuje pamäťové priestory aplikácií.

2.3. Premiestnenie

Pretože používame dynamické prideľovanie pamäte, pamäť priemernej aplikácie sa časom fragmentuje. Je to preto, že keď uvoľníme objekt uprostred pamäte, zostane tam medzera voľného priestoru. Postupom času sa tieto medzery hromadia a naša pamäť bude vyzerať ako šachovnica vyrobená zo striedajúcich sa oblastí voľného a použitého priestoru.

Samozrejme by sme sa mohli pokúsiť vyplniť tieto medzery novými objektmi. Aby sme to dosiahli, mali by sme prehľadať pamäť, či nemáme voľné miesto, ktoré je dostatočne veľké na to, aby držalo náš objekt. Je to nákladná operácia, najmä ak to musíme robiť zakaždým, keď chceme prideliť pamäť. Okrem toho bude pamäť stále fragmentovaná, pretože pravdepodobne nenájdeme voľné miesto s presnou veľkosťou, ktorú potrebujeme. Preto medzi objektmi budú medzery. Tieto medzery sú samozrejme menšie. Tiež sa môžeme pokúsiť tieto medzery minimalizovať, ale využíva to ešte viac výpočtovej sily.

Druhou stratégiou je často premiestniť objekty z oblastí fragmentovanej pamäte do voľných oblastí v kompaktnejšom formáte. Aby sme boli efektívnejší, rozdelili sme pamäťový priestor na bloky. Premiestnime všetky objekty v bloku alebo žiadny z nich. Týmto spôsobom bude alokácia pamäte rýchlejšia, pretože vieme, že v pamäti sú celé prázdne bloky.

2.4. Zber odpadu

Keď vytvárame aplikáciu Java, nemusíme uvoľňovať pamäť, ktorú sme pridelili, pretože smetiari to robia za nás. V súhrne Spoločnosť GC sleduje, na ktoré objekty sa z našej aplikácie môžeme dostať cez reťaz referencií, a uvoľňuje tie, na ktoré sa nedostaneme.

GC musí na vykonanie svojej práce sledovať stav objektov v halde. Napríklad je možný stav. To znamená, že aplikácia obsahuje odkaz na objekt. Tento odkaz môže byť prechodný. Jediná vec, na ktorej záleží, je, aby aplikácia mala prístup k týmto objektom prostredníctvom referencií. Ďalším príkladom je finalizácia: objekty, ku ktorým nemáme prístup. Toto sú objekty, ktoré považujeme za odpadky.

Aby to dosiahli, majú smetiari viac fáz.

2.5. Vlastnosti fázy GC

Fázy GC môžu mať rôzne vlastnosti:

  • a paralelne fáza môže bežať na viacerých vláknach GC
  • a sériový fáza beží na jednom vlákne
  • a stop-the-world fáza nemôže bežať súčasne s kódom aplikácie
  • a súbežne fáza môže bežať na pozadí, zatiaľ čo naša aplikácia robí svoju prácu
  • an prírastkové fáza môže skončiť pred dokončením všetkých svojich prác a pokračovať v nej neskôr

Upozorňujeme, že všetky vyššie uvedené techniky majú svoje silné a slabé stránky. Povedzme napríklad, že máme fázu, ktorá môže bežať súčasne s našou aplikáciou. Sériová implementácia tejto fázy vyžaduje 1% celkového výkonu procesora a trvá 1 000 ms. Naopak paralelná implementácia využíva 30% CPU a svoju prácu dokončí za 50 ms.

V tomto príklade paralelné riešenie celkovo využíva viac CPU, pretože môže byť zložitejšie a musí synchronizovať vlákna. Pre náročné aplikácie CPU (napríklad dávkové úlohy) je to problém, pretože máme menej výpočtového výkonu na vykonávanie užitočnej práce.

Tento príklad má samozrejme vymyslené čísla. Je však zrejmé, že všetky aplikácie majú svoje vlastnosti, takže majú odlišné požiadavky na GC.

Podrobnejší popis nájdete v našom článku o správe pamäte Java.

3. Koncepty ZGC

ZGC má v úmysle zabezpečiť medzikontinentálne fázy čo najkratšie. Dosahuje to takým spôsobom, že trvanie týchto časov pozastavenia sa s veľkosťou haldy nezvyšuje. Vďaka týmto vlastnostiam sa ZGC dobre hodí pre serverové aplikácie, kde sú bežné veľké hromady a vyžaduje sa rýchla doba odozvy aplikácie.

Okrem osvedčených postupov GC predstavuje ZGC aj nové koncepty, ktorým sa budeme venovať v nasledujúcich častiach.

Ale teraz sa pozrime na celkový obraz toho, ako ZGC funguje.

3.1. Veľký obraz

ZGC má fázu nazývanú značenie, kde nájdeme dosiahnuteľné objekty. GC môže ukladať informácie o stave objektu viacerými spôsobmi. Napríklad by sme mohli vytvoriť a Mapa, kde kľúčmi sú adresy pamäte a hodnotou je stav objektu na tejto adrese. Je to jednoduché, ale na uloženie týchto informácií je potrebná ďalšia pamäť. Udržiavanie takejto mapy môže byť tiež náročné.

ZGC používa iný prístup: ukladá referenčný stav ako bity referencie. Volá sa to referenčné sfarbenie. Týmto spôsobom však máme novú výzvu. Nastavenie bitov referencie na ukladanie metadát o objekte znamená, že na ten istý objekt môžu smerovať viaceré referencie, pretože stavové bity neobsahujú žiadne informácie o umiestnení objektu. Multimapping na záchranu!

Chceme tiež znížiť fragmentáciu pamäte. Spoločnosť ZGC na tento účel využíva premiestnenie. Ale s veľkou hromadou je premiestnenie pomalý proces. Pretože ZGC nechce dlhé doby pauzy, väčšinu premiestňovania vykonáva paralelne s aplikáciou. To však prináša nový problém.

Povedzme, že máme odkaz na objekt. ZGC ju premiestni a dôjde k prepnutiu kontextu, kde sa spustí vlákno aplikácie a pokúsi sa o prístup k tomuto objektu prostredníctvom svojej starej adresy. Spoločnosť ZGC to používa na riešenie prekážok nákladu. Zaťažovacia bariéra je kúsok kódu, ktorý sa spustí, keď vlákno načíta odkaz z haldy - napríklad keď pristupujeme k neprimitívnemu poľu objektu.

V ZGC bariéry zaťaženia kontrolujú bity metaúdajov referencie. V závislosti od týchto bitov ZGC môže vykonať určité spracovanie referencie skôr, ako ju dostaneme. Môže teda predložiť úplne iný odkaz. Hovoríme tomu premapovanie.

3.2. Značenie

ZGC rozdeľuje značenie na tri fázy.

Prvá fáza je fáza stop-the-world. V tejto fáze hľadáme koreňové referencie a označujeme ich. Koreňové odkazy sú východiskové body na dosiahnutie objektov v haldenapríklad lokálne premenné alebo statické polia. Pretože počet koreňových referencií je zvyčajne malý, je táto fáza krátka.

Ďalšia fáza je súčasná. V tejto fáze prechádzame grafom objektu a vychádzame z koreňových referencií. Označujeme každý objekt, ku ktorému sa dostaneme. Keď bariéra nákladu zistí neoznačený odkaz, tiež ho označí.

Posledná fáza je tiež fázou stop-the-world na zvládnutie niektorých okrajových prípadov, napríklad slabých referencií.

V tejto chvíli vieme, na ktoré objekty sa môžeme dostať.

ZGC používa označené0 a označené1 metadátové bity na označenie.

3.3. Referenčné sfarbenie

Referencia predstavuje pozíciu bajtu vo virtuálnej pamäti. Na to však nevyhnutne nemusíme použiť všetky bity odkazu - niektoré bity môžu predstavovať vlastnosti referencie. To je to, čo nazývame referenčné vyfarbenie.

S 32 bitmi dokážeme adresovať 4 gigabajty. Pretože v dnešnej dobe je rozšírené, že počítač má viac pamäte ako je táto, zjavne nemôžeme použiť žiadnu z týchto 32 bitov na vyfarbenie. Preto ZGC používa 64-bitové referencie. To znamená ZGC je k dispozícii iba na 64-bitových platformách:

Referencie ZGC používajú 42 bitov na reprezentáciu samotnej adresy. Výsledkom je, že referencie ZGC môžu adresovať 4 terabajty pamäťového priestoru.

Okrem toho máme 4 bity na ukladanie referenčných stavov:

  • finalizovateľné bit - objekt je prístupný iba cez finalizátor
  • premapovať bit - referencia je aktuálna a smeruje k aktuálnemu umiestneniu objektu (pozri premiestnenie)
  • označené0 a označené1 bity - slúžia na označenie dosiahnuteľných predmetov

Tieto bity sme tiež nazvali metadátové bity. V ZGC je presne jeden z týchto bitov metadát 1.

3.4. Premiestnenie

V ZGC pozostáva premiestnenie z nasledujúcich fáz:

  1. Súbežnú fázu, ktorá hľadá bloky, chceme premiestniť a umiestniť ich do sady premiestnenia.
  2. Fáza stop-the-world premiestni všetky koreňové referencie v skupine premiestnenia a aktualizuje ich referencie.
  3. Súbežná fáza premiestni všetky zostávajúce objekty v skupine premiestnenia a uloží mapovanie medzi starou a novou adresou v preposielacej tabuľke.
  4. K prepisovaniu zvyšných referencií dôjde v nasledujúcej fáze značenia. Takto nemusíme dvakrát prechádzať stromom objektov. Prípadne to môžu urobiť aj zvodidlá.

3.5. Premapovanie a bariéry zaťaženia

Upozorňujeme, že vo fáze premiestňovania sme neprepisovali väčšinu odkazov na premiestnené adresy. Preto pomocou týchto referencií by sme nemali prístup k objektom, ktoré sme chceli. A čo je ešte horšie, dostali sme sa k odpadu.

Spoločnosť ZGC používa na riešenie tohto problému bariéry nákladu. Bariéry zaťaženia fixujú odkazy smerujúce na premiestnené objekty technikou nazývanou premapovanie.

Keď aplikácia načíta referenciu, spustí bariéru načítania, ktorá potom podľa nasledujúcich krokov vráti správnu referenciu:

  1. Kontroluje, či premapovať bit je nastavený na 1. Ak áno, znamená to, že referencia je aktuálna, takže ju môžeme bezpečne vrátiť.
  2. Potom skontrolujeme, či bol odkazovaný objekt v skupine premiestnenia alebo nie. Ak nebol, znamená to, že sme ho nechceli premiestniť. Aby sme sa vyhli tejto kontrole nabudúce, keď načítame túto referenciu, nastavili sme premapovať bit na 1 a vráti aktualizovanú referenciu.
  3. Teraz vieme, že objekt, ku ktorému chceme získať prístup, bol cieľom premiestnenia. Jedinou otázkou je, či k premiestneniu došlo alebo nie? Ak bol objekt premiestnený, prejdeme na ďalší krok. V opačnom prípade ho teraz premiestnime a vytvoríme záznam v preposielacej tabuľke, v ktorom je uložená nová adresa pre každý premiestnený objekt. Potom pokračujeme ďalším krokom.
  4. Teraz vieme, že objekt bol premiestnený. Buď spoločnosťou ZGC, nami v predchádzajúcom kroku, alebo bariérou nákladu pri skoršom zásahu tohto objektu. Aktualizujeme tento odkaz na nové umiestnenie objektu (buď s adresou z predchádzajúceho kroku, alebo vyhľadaním v tabuľke na preposielanie), nastavíme premapovať bit, a vrátiť referenciu.

A je to, s vyššie uvedenými krokmi sme zabezpečili, že pri každom pokuse o prístup k objektu dostaneme najnovší odkaz na tento objekt. Pretože zakaždým, keď načítame referenciu, spustí sa bariéra zaťaženia. Znižuje preto výkon aplikácie. Najmä prvýkrát, keď pristupujeme k premiestnenému objektu. Ale toto je cena, ktorú musíme zaplatiť, ak chceme krátke doby pauzy. A keďže sú tieto kroky pomerne rýchle, nemá to výrazný vplyv na výkon aplikácie.

4. Ako povoliť ZGC?

Pri spustení našej aplikácie môžeme ZGC povoliť pomocou nasledujúcich možností príkazového riadku:

-XX: + UnlockExperimentalVMOptions -XX: + UseZGC

Pretože ZGC je experimentálny GC, bude chvíľu trvať, kým sa stane oficiálne podporovanou.

5. Záver

V tomto článku sme videli, že ZGC zamýšľa podporovať veľké haldy s nízkou dobou pozastavenia aplikácie.

Na dosiahnutie tohto cieľa využíva techniky vrátane farebných 64-bitových referencií, prekážok zaťaženia, premiestnenia a premapovania.


$config[zx-auto] not found$config[zx-overlay] not found