Uvod u ZGC: Skalabilan i eksperimentalni JVM sakupljač smeća s malim kašnjenjem

1. Uvod

Danas nisu rijetke situacije da aplikacije istovremeno opslužuju tisuće ili čak milijune korisnika. Takve aplikacije trebaju ogromnu količinu memorije. Međutim, upravljanje svom tom memorijom može lako utjecati na performanse aplikacije.

Da bi riješila ovaj problem, Java 11 predstavila je Z Garbage Collector (ZGC) kao eksperimentalnu implementaciju sakupljača smeća (GC).

U ovom uputstvu ćemo vidjeti kako ZGC uspijeva zadržati vremena niske pauze čak i na gomilama od više terabajta.

2. Glavni pojmovi

Da bismo razumjeli kako ZGC funkcionira, moramo razumjeti osnovne pojmove i terminologiju koja stoji iza upravljanja memorijom i sakupljača smeća.

2.1. Upravljanje memorijom

Fizička memorija je RAM koji pruža naš hardver.

Operativni sustav (OS) dodjeljuje prostor virtualne memorije za svaku aplikaciju.

Naravno, virtualnu memoriju pohranjujemo u fizičku memoriju, a OS je odgovoran za održavanje mapiranja između njih dvoje. Ovo mapiranje obično uključuje hardversko ubrzanje.

2.2. Višestruko mapiranje

Višestruko mapiranje znači da u virtualnoj memoriji postoje određene adrese, koje upućuju na istu adresu u fizičkoj memoriji. Budući da aplikacije pristupaju podacima putem virtualne memorije, ne znaju ništa o ovom mehanizmu (i ne trebaju).

Učinkovito, mapiramo više raspona virtualne memorije u isti raspon u fizičkoj memoriji:

Na prvi pogled slučajevi njegove upotrebe nisu očiti, ali vidjet ćemo kasnije da je ZGC potreban da učini svoju čaroliju. Također, pruža određenu sigurnost jer odvaja memorijske prostore aplikacija.

2.3. Preseljenje

Budući da koristimo dinamičko dodjeljivanje memorije, memorija prosječne aplikacije s vremenom postaje fragmentirana. To je zato što kad oslobodimo objekt usred memorije, tamo ostaje praznina slobodnog prostora. S vremenom se te praznine nakupljaju, a naše će sjećanje izgledati poput šahovske ploče izrađene od naizmjeničnih područja slobodnog i iskorištenog prostora.

Naravno, te bismo praznine mogli pokušati popuniti novim objektima. Da bismo to učinili, trebali bismo skenirati memoriju za slobodan prostor koji je dovoljno velik da sadrži naš objekt. To je skupa operacija, pogotovo ako to moramo učiniti svaki put kad želimo dodijeliti memoriju. Osim toga, memorija će i dalje biti fragmentirana, jer vjerojatno nećemo moći pronaći slobodan prostor koji ima točnu veličinu koja nam treba. Stoga će između predmeta nastati praznine. Naravno, ti su razmaci manji. Također, možemo pokušati smanjiti ove praznine, ali koristi još veću procesorsku snagu.

Druga strategija je često premjestiti objekte iz fragmentiranih područja memorije u slobodna područja u kompaktnijem formatu. Da bismo bili učinkovitiji, memorijski prostor dijelimo na blokove. Premještamo sve objekte u blok ili niti jedan od njih. Na taj će način dodjela memorije biti brža jer znamo da u memoriji postoje čitavi prazni blokovi.

2.4. Kolekcija smeća

Kada stvaramo Java aplikaciju, ne moramo oslobađati memoriju koju smo dodijelili, jer to čine umjesto nas sakupljači smeća. U sažetku, GC prati lanac referenci do kojih predmeta možemo doći iz naše aplikacije i oslobađa one do kojih ne možemo doći.

GC treba pratiti stanje objekata u prostoru hrpe da bi obavio svoj posao. Na primjer, moguće stanje je dostupno. To znači da aplikacija sadrži referencu na objekt. Ova referenca može biti prolazna. Važno je samo da aplikacija tim objektima može pristupiti putem referenci. Sljedeći je primjer moguće finalizirati: objekti kojima ne možemo pristupiti. To su predmeti koje smatramo smećem.

Da bi ga postigli, sakupljači smeća imaju više faza.

2.5. Svojstva faze GC

GC faze mogu imati različita svojstva:

  • a paralelno faza može raditi na više GC niti
  • a serijski faza radi na jednom navoju
  • a zaustavi svijet faza se ne može izvoditi istovremeno s kodom aplikacije
  • a istovremeno faza može raditi u pozadini, dok naša aplikacija radi svoj posao
  • an inkrementalni faza može završiti prije nego što završi sav svoj posao i nastaviti ga kasnije

Imajte na umu da sve gore navedene tehnike imaju svoje prednosti i nedostatke. Na primjer, recimo da imamo fazu koja se može izvoditi istovremeno s našom aplikacijom. Serijska implementacija ove faze zahtijeva 1% ukupnih performansi CPU-a i traje 1000 ms. Suprotno tome, paralelna implementacija koristi 30% CPU-a i završava svoj posao za 50 ms.

U ovom primjeru, paralelno rješenje koristi više CPU u cjelini, jer je možda složenije i mora sinkronizirati niti. Za CPU teške aplikacije (na primjer, skupne poslove) predstavlja problem jer imamo manje računalne snage za obavljanje korisnih poslova.

Naravno, ovaj primjer ima izmišljene brojeve. Međutim, jasno je da sve aplikacije imaju svoje karakteristike, pa imaju različite GC zahtjeve.

Za detaljnije opise posjetite naš članak o upravljanju Java memorijom.

3. ZGC koncepti

ZGC namjerava osigurati što kraće faze zaustavljanja svijeta. Postiže ga na takav način da se trajanje vremena pauze ne povećava s veličinom hrpe. Te karakteristike čine ZGC pogodnim za poslužiteljske aplikacije, gdje su česte velike gomile, a brzi odzivi aplikacija su uvjet.

Pored isprobanih i testiranih GC tehnika, ZGC uvodi nove koncepte, koje ćemo obraditi u sljedećim odjeljcima.

Ali za sada, pogledajmo cjelokupnu sliku o tome kako ZGC djeluje.

3.1. Velika slika

ZGC ima fazu koja se naziva markiranje, gdje pronalazimo dostupne predmete. GC može pohraniti informacije o stanju objekta na više načina. Na primjer, mogli bismo stvoriti Karta, gdje su ključevi adrese memorije, a vrijednost je stanje objekta na toj adresi. Jednostavno je, ali za pohranu tih podataka potrebna je dodatna memorija. Također, održavanje takve karte može biti izazov.

ZGC koristi drugačiji pristup: pohranjuje referentno stanje kao bitove reference. Zove se referentno bojanje. Ali na ovaj način imamo novi izazov. Postavljanje bitova reference za pohranu metapodataka o objektu znači da višestruke reference mogu upućivati ​​na isti objekt budući da državni bitovi ne sadrže nikakve informacije o mjestu objekta. Multimapiranje u pomoć!

Također želimo smanjiti fragmentaciju memorije. ZGC koristi preseljenje da bi to postigao. Ali s velikom hrpom, preseljenje je spor proces. Budući da ZGC ne želi dugo vrijeme pauze, većinu preseljenja radi paralelno s aplikacijom. Ali ovo uvodi novi problem.

Recimo da imamo referencu na objekt. ZGC ga premješta i dolazi do prebacivanja konteksta, gdje se nit aplikacije izvodi i pokušava pristupiti ovom objektu putem njegove stare adrese. ZGC koristi prepreke opterećenja da to riješi. Zapreka učitavanja je dio koda koji se izvodi kada nit učita referencu iz hrpe - na primjer, kada pristupamo neprimitivnom polju objekta.

U ZGC-u prepreke opterećenja provjeravaju bitove metapodataka reference. Ovisno o tim bitovima, ZGC može izvršiti određenu obradu reference prije nego što je dobijemo. Stoga bi moglo proizvesti sasvim drugu referencu. To nazivamo remapiranjem.

3.2. Obilježava

ZGC razbija obilježavanje u tri faze.

Prva faza je faza zaustavljanja svijeta. U ovoj fazi tražimo korijenske reference i označavamo ih. Korijenske reference početne su točke za dosezanje objekata u hrpi, na primjer, lokalne varijable ili statička polja. Budući da je broj korijenskih referenci obično mali, ova je faza kratka.

Sljedeća faza je istodobna. U ovoj fazi, prelazimo graf objekta, počevši od korijenskih referenci. Označavamo svaki predmet do kojeg dođemo. Također, kada prepreka tereta otkrije neobilježenu referencu, ona je također označava.

Posljednja faza je također faza zaustavljanja svijeta za rješavanje nekih rubnih slučajeva, poput slabih referenci.

U ovom trenutku znamo do kojih objekata možemo doći.

ZGC koristi označena0 i označena1 bitovi metapodataka za označavanje.

3.3. Referentno bojanje

Referenca predstavlja položaj bajta u virtualnoj memoriji. Međutim, za to ne moramo nužno koristiti sve bitove reference - neki bitovi mogu predstavljati svojstva reference. To nazivamo referentnim bojanjem.

Sa 32 bita možemo adresirati 4 gigabajta. Budući da je danas rašireno da računalo ima više memorije od ove, očito ne možemo koristiti nijedan od ova 32 bita za bojanje. Stoga se ZGC koristi 64-bitnim referencama. To znači ZGC je dostupan samo na 64-bitnim platformama:

ZGC reference koriste 42 bita za predstavljanje same adrese. Kao rezultat toga, ZGC reference mogu adresirati 4 terabajta memorijskog prostora.

Povrh toga, imamo 4 bita za pohranu referentnih stanja:

  • konačan bit - objekt je dostupan samo finalizatorom
  • remap bit - referenca je ažurna i pokazuje na trenutno mjesto objekta (vidi preseljenje)
  • označena0 i označena1 bitovi - koriste se za označavanje dostupnih objekata

Te smo bitove također zvali bitovi metapodataka. U ZGC-u je upravo jedan od ovih bitova metapodataka 1.

3.4. Preseljenje

U ZGC preseljenje se sastoji od sljedećih faza:

  1. Istodobna faza, koja traži blokove, želimo preseliti i staviti ih u skup preseljenja.
  2. Faza zaustavljanja svijeta premješta sve korijenske reference u skupu preseljenja i ažurira njihove reference.
  3. Istodobna faza premješta sve preostale objekte u skupu premještanja i pohranjuje mapiranje između stare i nove adrese u tablici prosljeđivanja.
  4. Prepisivanje preostalih referenci događa se u sljedećoj fazi označavanja. Na taj način, ne moramo dva puta prelaziti stablo objekata. To mogu učiniti i prepreke za teret.

3.5. Pregrađivanje i barijere opterećenja

Imajte na umu da u fazi preseljenja nismo prepisali većinu referenci na preseljene adrese. Stoga, koristeći te reference, ne bismo pristupili objektima koje smo željeli. Još gore, mogli bismo pristupiti smeću.

ZGC koristi prepreke opterećenja da riješi ovaj problem. Prepreke opterećenja popravljaju reference koje upućuju na premještene objekte tehnikom koja se naziva remapiranje.

Kada aplikacija učita referencu, ona pokreće prepreku opterećenja, koja zatim slijedi sljedeće korake za vraćanje ispravne reference:

  1. Provjerava je li remap bit je postavljen na 1. Ako je tako, to znači da je referenca ažurirana, pa je možemo sigurno vratiti.
  2. Zatim provjeravamo je li referencirani objekt bio u skupu preseljenja ili nije. Ako nije, to znači da ga nismo željeli preseliti. Da bismo izbjegli ovu provjeru kad sljedeći put učitamo ovu referencu, postavili smo remap bit na 1 i vratiti ažuriranu referencu.
  3. Sada znamo da je objekt kojem želimo pristupiti bio cilj preseljenja. Pitanje je samo je li se preseljenje dogodilo ili nije? Ako je objekt premješten, preskačemo na sljedeći korak. Inače, sada ga premještamo i stvaramo unos u tablici za prosljeđivanje koji pohranjuje novu adresu za svaki premješteni objekt. Nakon ovoga nastavljamo sa sljedećim korakom.
  4. Sada znamo da je objekt premješten. Ili ZGC, mi u prethodnom koraku, ili barijera tereta tijekom ranijeg pogotka ovog objekta. Ažuriramo ovu referencu na novo mjesto objekta (s adresom iz prethodnog koraka ili potragom u tablici za prosljeđivanje), postavljamo remap bit i vratite referencu.

I to je to, gornjim koracima osigurali smo da svaki put kada pokušamo pristupiti objektu dobijemo najnoviju referencu na njega. Budući da svaki put kad učitamo referencu, ona pokreće prepreku opterećenja. Stoga smanjuje performanse aplikacije. Pogotovo prvi put kada pristupamo preseljenom objektu. Ali ovo je cijena koju moramo platiti ako želimo kratka vremena pauze. A budući da su ovi koraci relativno brzi, to ne utječe značajno na izvedbu aplikacije.

4. Kako omogućiti ZGC?

ZGC možemo omogućiti sa sljedećim opcijama naredbenog retka prilikom pokretanja naše aplikacije:

-XX: + OtključajEksperimentalneVMOpcije -XX: + Koristi ZGC

Imajte na umu da će, budući da je ZGC eksperimentalni GC, trebati neko vrijeme da postane službeno podržan.

5. Zaključak

U ovom smo članku vidjeli da ZGC namjerava podržati velike veličine hrpe s malim vremenima pauze.

Da bi postigao taj cilj, koristi tehnike, uključujući obojene 64-bitne reference, prepreke opterećenja, preseljenje i ponovno mapiranje.


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