Programovanie

Optimalizácia výkonu JVM, 3. časť: Zber odpadu

Mechanizmus zberu odpadu platformy Java výrazne zvyšuje produktivitu vývojárov, ale zle implementovaný zberač odpadu môže nadmerne spotrebovať aplikačné zdroje. V tomto treťom článku v Optimalizácia výkonu JVM série, Eva Andreasson ponúka začiatočníkom Java prehľad pamäťového modelu platformy Java a mechanizmu GC. Potom vysvetľuje, prečo je fragmentácia (a nie GC) to hlavné „mám to!“ výkonu aplikácií Java a prečo je generačný zber odpadu a zhutňovanie v súčasnosti hlavným (aj keď nie najinovatívnejším) prístupom k riadeniu fragmentácie haldy v aplikáciách Java.

Zber odpadu (GC) je proces, ktorého cieľom je uvoľniť obsadenú pamäť, na ktorú už neodkazuje žiadny dosiahnuteľný objekt Java, a je nevyhnutnou súčasťou systému dynamickej správy pamäte virtuálneho počítača Java (JVM). V typickom cykle zberu odpadu sa uchovávajú všetky objekty, na ktoré sa stále odkazuje, a teda ktoré sú dosiahnuteľné. Miesto obsadené objektmi, na ktoré sa predtým odkazovalo, sa uvoľní a uvoľní, aby sa umožnilo pridelenie nových objektov.

Aby ste pochopili zhromažďovanie odpadu a rôzne prístupy a algoritmy GC, musíte najskôr vedieť niekoľko vecí o pamäťovom modeli platformy Java.

Optimalizácia výkonu JVM: Prečítajte si sériu

  • Časť 1: Prehľad
  • Časť 2: Prekladače
  • 3. časť: Zber odpadu
  • Časť 4: Súčasné zhutňovanie GC
  • Časť 5: Škálovateľnosť

Zber odpadu a pamäťový model platformy Java

Keď zadáte možnosť spustenia -Xmx na príkazovom riadku vašej aplikácie Java (napríklad: java -Xmx: 2g MyApp) pamäť je priradená procesu Java. Táto pamäť sa označuje ako Halda Java (alebo len halda). Toto je vyhradený adresný priestor pamäte, kde budú alokované všetky objekty vytvorené vaším programom Java (alebo niekedy JVM). Keď bude váš program Java neustále bežať a prideľovať nové objekty, hromada Java (tj. Adresný priestor) sa zaplní.

Halda Java bude nakoniec plná, čo znamená, že alokačné vlákno nedokáže nájsť dostatočne veľkú po sebe idúcu časť voľnej pamäte pre objekt, ktorý chce prideliť. V tom okamihu JVM určí, že je potrebné uskutočniť odvoz odpadu, a upozorní na to zberača odpadu. Zber odpadu sa dá spustiť aj pri vyvolaní programu Java System.gc (). Použitím System.gc () nezaručuje odvoz odpadu. Než bude možné zahájiť zhromažďovanie odpadu, mechanizmus GC najskôr určí, či je bezpečné ho spustiť. Spustenie zberu odpadu je bezpečné, keď sú všetky aktívne vlákna aplikácie v bezpečnom bode, ktorý to umožňuje, napr. jednoducho vysvetlené, že by bolo zlé začať zbierať odpadky uprostred prebiehajúceho prideľovania objektov alebo uprostred vykonávania postupnosti optimalizovaných pokynov CPU (pozri môj predchádzajúci článok o kompilátoroch), pretože by ste mohli stratiť kontext a tým pokaziť koniec výsledky.

Smetiar by mal nikdy získať späť aktívne odkazovaný objekt; by to porušilo špecifikáciu virtuálneho stroja Java. Od zberača odpadu sa tiež nevyžaduje, aby okamžite zbieral mŕtve predmety. Mŕtve predmety sa nakoniec zbierajú počas nasledujúcich cyklov zberu odpadu. Aj keď existuje veľa spôsobov, ako implementovať odvoz odpadu, tieto dva predpoklady platia pre všetky odrody. Skutočnou výzvou pri zhromažďovaní odpadu je identifikovať všetko, čo je živé (stále sa na neho odkazuje) a získať späť akúkoľvek neodkázanú pamäť, ale urobte to bez toho, aby to malo dopad na bežiace aplikácie, čo je viac ako nevyhnutné. Zberateľ odpadu má teda dva mandáty:

  1. Rýchlo uvoľniť nereferovanú pamäť, aby sa uspokojila rýchlosť alokácie aplikácie, aby sa jej neminula pamäť.
  2. Získanie pamäte pri minimálnom dopade na výkon (napr. Latenciu a priepustnosť) spustenej aplikácie.

Dva druhy zberu odpadu

V prvom článku v tejto sérii som sa dotkol dvoch hlavných prístupov k zberu odpadu, ktorými sú zberatelia počítania a sledovania referencií. Tentokrát sa budem podrobnejšie venovať každému prístupu a potom predstavím niektoré z algoritmov používaných na implementáciu sledovacích kolektorov v produkčných prostrediach.

Prečítajte si sériu optimalizácií výkonu JVM

  • Optimalizácia výkonu JVM, časť 1: Prehľad
  • Optimalizácia výkonu JVM, časť 2: Kompilátory

Zberatelia referenčného počítania

Zberatelia referenčného počítania sledujte, koľko referencií smeruje na každý objekt Java. Keď sa počet objektov stane nulovým, pamäť sa dá okamžite znovu získať. Tento okamžitý prístup k regenerovanej pamäti je hlavnou výhodou prístupu spočítavania odkazov na odvoz odpadu. Existuje len veľmi málo réžie, pokiaľ ide o držanie nereferencovanej pamäte. Udržiavanie všetkých referenčných počtov aktuálnych môže byť však dosť nákladné.

Hlavnou ťažkosťou so zberačmi počítania referencií je udržiavanie presných počtov referencií. Ďalšou dobre známou výzvou je zložitosť spojená s manipuláciou s kruhovými štruktúrami. Ak sa dva objekty navzájom odkazujú a žiadny živý objekt sa na ne neodkazuje, ich pamäť sa nikdy neuvoľní. Oba objekty navždy zostanú s nenulovým počtom. Znovuzískanie pamäte súvisiace s kruhovými štruktúrami si vyžaduje dôkladnú analýzu, ktorá prináša nákladné náklady na algoritmus, a teda aj na aplikáciu.

Trasovanie zberateľov

Trasovanie zberateľov sú založené na predpoklade, že všetky živé objekty je možné nájsť iteračným sledovaním všetkých odkazov a následných odkazov z počiatočnej množiny známych ako živé objekty. Počiatočná sada živých objektov (tzv koreňové objekty alebo len tak korene (skrátene) sa nachádzajú analýzou registrov, globálnych polí a rámcov zásobníkov v okamihu, keď je spustený zber odpadu. Po identifikácii počiatočnej živej množiny sleduje sledovací kolektor odkazy z týchto objektov a zaradí ich do frontu, aby sa označili ako živé, a následne ich odkazy budú sledované. Označenie všetkých nájdených odkazovaných objektov žiť znamená, že známa živá zostava sa časom zvyšuje. Tento proces pokračuje, kým nebudú nájdené a označené všetky odkazované (a teda všetky živé) objekty. Keď kolektor sledovania nájde všetky živé objekty, získa späť zostávajúcu pamäť.

Stopovacie kolektory sa líšia od kolektorov na počítanie referencií v tom, že dokážu pracovať s kruhovými štruktúrami. Úlovok u väčšiny sledovacích kolektorov je fáza označovania, ktorá znamená čakanie, kým bude možné získať späť nereferovanú pamäť.

Sledovacie kolektory sa najčastejšie používajú na správu pamäte v dynamických jazykoch; sú zďaleka najbežnejšie pre jazyk Java a sú komerčne overené v produkčných prostrediach už mnoho rokov. Zameriam sa na sledovanie zberačov pre zvyšok tohto článku, počnúc niektorými algoritmami, ktoré implementujú tento prístup k zberu odpadu.

Sledovacie algoritmy kolektora

Kopírovanie a označiť a pozametať zber odpadu nie sú nové, ale stále sú to dva najbežnejšie algoritmy, ktoré dnes implementujú sledovanie zberu odpadu.

Kopírovanie zberateľov

Tradiční zberatelia kopírovania používajú a z vesmíru a a do vesmíru - to znamená dva samostatne definované adresné priestory haldy. V mieste zberu odpadu sa živé objekty v oblasti definovanej ako z vesmíru skopírujú do ďalšieho dostupného priestoru v oblasti definovanej ako priestor. Keď sú všetky živé objekty v kozmickom priestore presunuté, je možné získať späť celý kozmický priestor. Keď alokácia začína znova, začína sa od prvého voľného miesta v priestore.

V starších implementáciách tohto algoritmu sa prepínajú miesta z vesmíru a z vesmíru, čo znamená, že keď je priestor plný, znova sa spustí zber odpadu a z vesmíru sa stane priestor, ako je to znázornené na obrázku 1.

Modernejšie implementácie kopírovacieho algoritmu umožňujú priradiť ľubovoľné adresné priestory v rámci haldy ako medzery a medzery. V týchto prípadoch si nevyhnutne nemusia navzájom meniť polohu; skôr sa z každého stane iný adresný priestor v halde.

Jednou z výhod kopírovania kolektorov je, že objekty sú v priestore tesne alokované, čo úplne eliminuje fragmentáciu. Fragmentácia je bežný problém, s ktorým iné algoritmy na zhromažďovanie odpadu zápasia; o čom budem hovoriť ďalej v tomto článku.

Nevýhody kopírovania zberateľov

Kopírujúci zberatelia sú zvyčajne stop-the-world zberatelia, čo znamená, že pokiaľ je cyklus zberu odpadu v cykle, nie je možné vykonať žiadnu prácu s aplikáciou. Pri okamžitej implementácii platí, že čím väčšia je plocha, ktorú musíte kopírovať, tým vyšší bude dopad na výkon vašej aplikácie. To je nevýhoda pre aplikácie, ktoré sú citlivé na čas odozvy. Pri kopírovacom kolektore musíte brať do úvahy aj najhorší scenár, keď je všetko živé z vesmíru. Vždy musíte nechať dostatok priestoru na presun živých predmetov, čo znamená, že priestor musí byť dostatočne veľký na to, aby hostil všetko v priestore. Algoritmus kopírovania je z dôvodu tohto obmedzenia mierne pamäťovo neefektívny.

Zberatelia značiek

Väčšina komerčných JVM nasadených v podnikových produkčných prostrediach prevádzkuje označovacie a zberné (alebo označovacie) kolektory, ktoré nemajú taký výkonový dopad ako kopírujúce kolektory. Medzi najznámejšie zberače značenia patria CMS, G1, GenPar a DeterministicGC (pozri zdroje).

A zberač značiek a zametacích strojov sleduje odkazy a označuje každý nájdený objekt bitom "live". Zvyčajne množina bitov zodpovedá adrese alebo v niektorých prípadoch množine adries na halde. Živý bit možno napríklad uložiť ako bit v hlavičke objektu, bitovom vektore alebo bitovej mape.

Keď bude všetko označené naživo, naštartuje sa fáza zametania. Ak má zberateľ fázu zametania, v zásade obsahuje nejaký mechanizmus na opätovné prechádzanie haldy (nielen živú množinu, ale celú dĺžku haldy) na vyhľadanie všetkých neoznačených kúsky po sebe idúcich adresných priestorov pamäte. Neoznačená pamäť je zadarmo a je možné ju znovu získať. Zberateľ potom spojí tieto neoznačené časti do organizovaných bezplatných zoznamov. V zberači odpadkov môžu byť rôzne bezplatné zoznamy - zvyčajne sú usporiadané podľa veľkostí kusov. Niektoré JVM (napríklad JRockit Real Time) implementujú kolektory s heuristikou, ktoré dynamicky vytvárajú zoznamy rozsahov veľkostí na základe údajov o profilovaní aplikácií a štatistík veľkosti objektov.

Po dokončení fázy zametania sa prideľovanie začne znova. Nové alokačné oblasti sú alokované z voľných zoznamov a bloky pamäte by sa mohli zhodovať s veľkosťami objektov, priemermi veľkostí objektov na ID vlákna alebo veľkosťami aplikácie TLAB vyladenými. Prispôsobenie voľného priestoru čo najbližšie veľkosti toho, čo sa vaša aplikácia pokúša prideliť, optimalizuje pamäť a mohlo by pomôcť znížiť fragmentáciu.

Viac informácií o veľkostiach TLAB

O rozdelení TLAB a TLA (Thread Local Allocation Buffer alebo Thread Local Area) sa hovorí v časti 1 o optimalizácii výkonu JVM.

Nevýhody zberateľov značiek a zametačov

Fáza označenia závisí od množstva živých údajov na vašej halde, zatiaľ čo fáza zametania závisí od veľkosti haldy. Pretože musíte počkať, kým sa obaja známka a zamiesť fázy sú úplné na získanie pamäte, tento algoritmus spôsobuje problémy s pauzou pre väčšie hromady a väčšie súbory živých údajov.

Jedným zo spôsobov, ako môžete pomôcť aplikáciám náročným na pamäť, je použitie možností ladenia GC, ktoré vyhovejú rôznym scenárom a potrebám aplikácie. Ladenie môže v mnohých prípadoch pomôcť prinajmenšom odložiť jednu z týchto fáz z toho, aby sa stala rizikom pre vašu aplikáciu alebo dohody o úrovni služieb (SLA). (SLA špecifikuje, že aplikácia bude spĺňať určité časy odozvy aplikácie - t. J. Latenciu.) Ladenie pre každú zmenu načítania a modifikáciu aplikácie je opakujúca sa úloha, pretože ladenie je platné iba pre konkrétnu záťaž a rýchlosť alokácie.

Implementácie značkovania

Existujú najmenej dva komerčne dostupné a overené prístupy k implementácii zbierky známkových a zametacích. Jedným z nich je paralelný prístup a druhým je súbežný (alebo väčšinou súbežný) prístup.

Paralelné kolektory

Paralelný zber znamená, že prostriedky pridelené procesu sa používajú paralelne na účely zberu odpadu. Väčšina komerčne implementovaných paralelných kolektorov sú monolitické stop-the-world kolektory - všetky aplikačné vlákna sú zastavené, kým nie je dokončený celý cyklus zberu odpadu. Zastavenie všetkých vlákien umožňuje efektívne a paralelné efektívne využitie všetkých prostriedkov na dokončenie zberu odpadu prostredníctvom fáz označenia a zametania. To vedie k veľmi vysokej úrovni efektívnosti, čo zvyčajne vedie k vysokému skóre v benchmarkoch priepustnosti, ako je SPECjbb. Ak je priepustnosť pre vašu aplikáciu nevyhnutná, vynikajúcou voľbou je paralelný prístup.

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