Programiranje

Optimizacija delovanja JVM, 3. del: Zbiranje smeti

Mehanizem zbiranja smeti na platformi Java močno poveča produktivnost razvijalcev, vendar lahko slabo uveden zbiralec smeti preveč porabi vire aplikacij. V tem tretjem članku v Optimizacija delovanja JVM Eva Andreasson ponuja začetnikom Java pregled nad pomnilniškim modelom in mehanizmom GC platforme Java. Nato pojasni, zakaj je razdrobljenost (in ne GC) glavni "razumevanje!" uspešnosti aplikacij Java in zakaj sta generacijsko zbiranje in zbijanje smeti trenutno vodilna (čeprav ne najbolj inovativna) pristopa k upravljanju fragmentacije kopice v aplikacijah Java.

Odvoz smeti (GC) je proces, katerega namen je sprostiti zasedeni pomnilnik, na katerega se ne sklicuje noben dosegljiv objekt Java, in je bistveni del sistema za dinamično upravljanje pomnilnika Java Virtual Machine (JVM). V običajnem ciklu zbiranja smeti se hranijo vsi predmeti, na katere se še vedno sklicuje in so zato dosegljivi. Prostor, ki ga zasedajo predhodno navedeni predmeti, se sprosti in predela, da se omogoči dodelitev novih predmetov.

Da bi razumeli zbiranje smeti in različne pristope in algoritme GC, morate najprej vedeti nekaj stvari o pomnilniškem modelu platforme Java.

Optimizacija delovanja JVM: preberite serijo

  • 1. del: Pregled
  • 2. del: Prevajalci
  • 3. del: Odvoz smeti
  • 4. del: Hkratno stiskanje GC
  • 5. del: Razširljivost

Zbiranje smeti in pomnilniški model platforme Java

Ko določite možnost zagona -Xmx v ukazni vrstici aplikacije Java (na primer: java -Xmx: 2g MyApp) pomnilnik je dodeljen procesu Java. Ta spomin se imenuje Java kopica (ali samo kup). To je namenski pomnilniški naslovni prostor, kjer bodo dodeljeni vsi predmeti, ki jih ustvari vaš program Java (ali včasih JVM). Ko se vaš program Java še naprej izvaja in dodeljuje nove predmete, se bo kopica Java (kar pomeni naslovni prostor) zapolnila.

Sčasoma bo kopica Java polna, kar pomeni, da dodelitvena nit ne more najti dovolj velikega zaporednega odseka prostega pomnilnika za predmet, ki ga želi dodeliti. Na tej točki JVM ugotovi, da mora biti odvoz smeti, in obvesti zbiralca smeti. Zbiranje smeti se lahko sproži tudi, ko pokliče program Java System.gc (). Uporaba System.gc () ne zagotavlja odvoza smeti. Preden se lahko zbiranje smeti začne, mehanizem GC najprej ugotovi, ali je varno zagnati ga. Varno lahko začnete z zbiranjem smeti, ko so vse aktivne niti aplikacije na varni točki, da to omogočite, npr. preprosto razloženo, da bi bilo slabo začeti zbiranje smeti sredi tekoče dodelitve predmetov ali med izvajanjem zaporedja optimiziranih navodil CPU (glej moj prejšnji članek o prevajalnikih), saj lahko izgubite kontekst in s tem zmedete konec rezultatov.

Zbiralnik smeti bi moral nikoli povrniti predmet, na katerega se aktivno sklicuje; če bi to storili, bi prekinili specifikacijo navideznega stroja Java. Zbiralcu smeti tudi ni treba takoj zbirati mrtvih predmetov. Odmrli predmeti se sčasoma zbirajo med naslednjimi cikli zbiranja smeti. Čeprav obstaja veliko načinov za izvajanje odvoza smeti, ti dve predpostavki veljata za vse sorte. Resnični izziv zbiranja smeti je prepoznati vse, kar je v živo (na katerega se še vedno sklicuje), in povrniti kakršen koli nereferenciran pomnilnik, vendar to storite, ne da bi to vplivalo na zagnane programe več kot je potrebno. Zbiralnik smeti ima tako dva mandata:

  1. Hitro osvoboditi nereferencirani pomnilnik, da bi zadostili stopnji dodeljevanja aplikacije, tako da ji ne zmanjka pomnilnika.
  2. Če želite povrniti pomnilnik, hkrati pa minimalno vplivati ​​na zmogljivost (npr. Zakasnitev in prepustnost) delujoče aplikacije.

Dve vrsti odvoza smeti

V prvem članku v tej seriji sem se dotaknil dveh glavnih pristopov k zbiranju smeti, ki sta štetje in sledenje zbiralcev referenc. Tokrat bom podrobneje razčlenil vsak pristop, nato pa predstavil nekaj algoritmov, ki se uporabljajo za izvajanje zbiralnikov sledenja v proizvodnih okoljih.

Preberite serijo optimizacije delovanja JVM

  • Optimizacija delovanja JVM, 1. del: Pregled
  • Optimizacija delovanja JVM, 2. del: Prevajalniki

Zbiralci referenčnega štetja

Zbiralci referenčnega štetja spremljajte, koliko referenc kaže na posamezen objekt Java. Ko število predmetov postane nič, lahko pomnilnik takoj ponovno pridobite. Takojšen dostop do obnovljenega pomnilnika je glavna prednost pristopa štetja referenc pri zbiranju smeti. Pri držanju nereferenčnega pomnilnika je zelo malo režijskih stroškov. Posodabljanje vseh referenčnih števcev je lahko zelo drago.

Glavna težava pri zbiralcih štetja referenc je vzdrževanje natančnosti štetja referenc. Drug dobro znan izziv je zapletenost pri ravnanju s krožnimi konstrukcijami. Če se dva predmeta sklicujeta drug na drugega in se noben predmet v živo ne sklicuje nanje, njihov spomin ne bo nikoli sproščen. Oba predmeta bosta za vedno ostala brez števila nič. Pridobitev pomnilnika, povezanega s krožnimi strukturami, zahteva veliko analizo, ki algoritmu in s tem aplikaciji prinese drage režijske stroške.

Zbiralci sledenja

Zbiralci sledenja temeljijo na predpostavki, da je mogoče vse žive predmete najti s ponavljajočim se sledenjem vseh referenc in poznejših referenc iz začetnega nabora, za katere je znano, da so živi predmeti. Začetni nabor predmetov v živo (imenovan korenski predmeti ali samo korenine na kratko) se nahajajo z analizo registrov, globalnih polj in okvirov skladov v trenutku, ko se sproži zbiranje smeti. Ko je identificiran začetni niz v živo, zbiralec sledenja sledi referencam iz teh predmetov in jih postavi v čakalne vrste, da jih označi kot žive, nato pa sledi njihovim referencam. Označevanje vseh najdenih referenčnih predmetov v živo pomeni, da se znani niz v živo sčasoma povečuje. Ta postopek se nadaljuje, dokler ne najdejo in označijo vseh referenčnih (in s tem vseh živih) predmetov. Ko bo zbiralec sledenja našel vse žive predmete, bo povrnil preostali pomnilnik.

Sledilni kolektorji se od referenčnih števcev razlikujejo po tem, da lahko obvladujejo krožne strukture. Ulov večine zbiralcev sledenja je faza označevanja, ki pomeni čakanje, preden lahko ponovno pridobi nereferenčni pomnilnik.

Zbiralniki sledenja se najpogosteje uporabljajo za upravljanje pomnilnika v dinamičnih jezikih; so daleč najpogostejši za jezik Java in so že vrsto let komercialno dokazani v proizvodnih okoljih. Do konca tega članka se bom osredotočil na zbiranje zbiralcev, začenši z nekaterimi algoritmi, ki izvajajo ta pristop k zbiranju smeti.

Algoritmi zbiranja sledenja

Kopiranje in mark-and-sweep zbiranje smeti sicer ni novo, vendar sta še vedno najpogostejša algoritma, ki danes izvajata sledenje zbiranju smeti.

Zbiratelji kopij

Tradicionalni zbiralci kopij uporabljajo a iz vesolja in a v vesolje - to je dva ločeno določena naslovna prostora na kopici. Na mestu zbiranja smeti se živi predmeti znotraj območja, ki je opredeljeno kot od-vesolje, kopirajo v naslednji razpoložljivi prostor znotraj območja, opredeljenega kot do-prostora. Ko se vsi živi predmeti v vesolju odselijo, je mogoče ves iz vesolja pridobiti nazaj. Ko se dodelitev začne znova, se začne s prve proste lokacije v prostoru.

Pri starejših izvedbah tega algoritma se preklopi prostor iz vesolja in vesolja, kar pomeni, da se, ko je prostor vesolja poln, ponovno sproži zbiranje smeti in prostor postane prostor iz prostora, kot je prikazano na sliki 1.

Sodobnejše izvedbe algoritma za kopiranje omogočajo, da se poljubnim naslovnim prostorom znotraj kopice dodeli prostor in prostor. V teh primerih jim ni treba med seboj zamenjati lokacije; namesto tega vsak postane drug naslovni prostor znotraj kupa.

Ena prednost kopiranja zbiralcev je, da so predmeti tesno razporejeni skupaj v vesolje in popolnoma odpravijo razdrobljenost. Razdrobljenost je pogosta težava, s katero se spopadajo drugi algoritmi za odvoz smeti; nekaj, o čemer bom razpravljal kasneje v tem članku.

Slabe strani zbiralcev kopij

Kopiralci so običajno zbiralci stop-the-world, kar pomeni, da nobenega aplikacijskega dela ni mogoče izvesti, dokler je odvoz smeti v ciklu. Pri uvajanju programa Stop-the-world, večje kot je območje, ki ga morate kopirati, večji bo vpliv na delovanje vaše aplikacije. To je pomanjkljivost za aplikacije, ki so občutljive na odzivni čas. Pri zbiralniku kopij morate upoštevati tudi najslabši možni scenarij, ko je vse v živo iz vesolja. Vedno morate pustiti dovolj prostora za premikanje predmetov v živo, kar pomeni, da mora biti vesolje dovolj veliko, da lahko gosti vse v vesolju. Algoritem kopiranja je zaradi te omejitve rahlo neučinkovit.

Označevalci in pometalci

Večina komercialnih JVM-jev, nameščenih v proizvodnih okoljih podjetja, uporablja zbiralnike označevanja in pometanja (ali označevanja), ki nimajo vpliva na zmogljivost, ki ga imajo zbiralniki kopiranja. Nekateri najbolj znani zbiralci oznak so CMS, G1, GenPar in DeterministicGC (glejte Viri).

A zbiralnik oznak izsledi reference in označi vsak najdeni predmet z bitom "v živo". Običajno nastavljeni bit ustreza naslovu ali v nekaterih primerih naboru naslovov na kupu. Bit v živo lahko na primer shranite kot bit v glavi predmeta, bitni vektor ali bitni zemljevid.

Ko je vse označeno v živo, se začne faza pometanja. Če ima zbiralec fazo čiščenja, v bistvu vključuje nek mehanizem za ponovno prehajanje kupa (ne samo nabora v živo, ampak celotno dolžino kupa), da poišče vse neoznačene kosov zaporednih pomnilniških prostorov. Neoznačen pomnilnik je brezplačen in obnovljiv. Nato zbiralec te neoznačene kose poveže v organizirane brezplačne sezname. V zbiralniku smeti so lahko različni brezplačni seznami - običajno organizirani po velikostih kosov. Nekateri JVM-ji (na primer JRockit Real Time) izvajajo zbiralce z hevristiko, ki dinamično navaja seznam obsegov na podlagi podatkov profiliranja aplikacij in statistike velikosti objekta.

Ko je faza čiščenja končana, se dodelitev začne znova. Nova območja dodelitve so dodeljena s prostih seznamov, pomnilniške koščke pa je mogoče ujemati z velikostmi predmetov, povprečji velikosti predmetov na ID niti ali velikostmi TLAB, nastavljenimi za aplikacije. Če prilagodite prosti prostor velikosti tistega, kar poskuša dodeliti vaša aplikacija, optimizira pomnilnik in lahko pomaga zmanjšati razdrobljenost.

Več o velikostih TLAB

Delitev TLAB in TLA (Thread Local Allocation Buffer ali Thread Local Area) je obravnavana v 1. delu optimizacije delovanja JVM.

Slabe strani zbiralcev za označevanje in pometanje

Faza označevanja je odvisna od količine podatkov v živo na vašem kupu, medtem ko je faza čiščenja odvisna od velikosti kupa. Ker morate počakati, da oba oznaka in pometanje faze so popolne za povrnitev pomnilnika, ta algoritem povzroča izzive za čas premora za večje kupe in večje nabore podatkov v živo.

Eden od načinov, kako lahko močno pomagate aplikacijam, je uporaba možnosti za nastavitev GC, ki ustrezajo različnim scenarijem in potrebam aplikacij. Uglaševanje lahko v mnogih primerih pomaga vsaj pri preložitvi katere koli od teh faz, da postane tveganje za vašo aplikacijo ali sporazume na ravni storitve (SLA). (SLA določa, da bo aplikacija ustrezala določenim odzivnim časom aplikacije - tj. Zakasnitvi.) Nastavitev za vsako spremembo obremenitve in spremembo aplikacije je ponavljajoča se naloga, saj je nastavitev veljavna le za določeno delovno obremenitev in stopnjo dodeljevanja.

Izvedbe označevanja in čiščenja

Obstajata vsaj dva komercialno dostopna in preizkušena pristopa za izvajanje zbiranja oznak in pometa. Eden je vzporedni pristop, drugi pa sočasni (ali večinoma sočasni) pristop.

Vzporedni kolektorji

Vzporedno zbiranje pomeni, da se viri, dodeljeni procesu, uporabljajo vzporedno za odvoz smeti. Večina komercialno izvedenih vzporednih zbiralnikov je monolitnih zbiralnikov "ustavi svet" - vse niti aplikacij se ustavijo, dokler ni zaključen celoten cikel zbiranja smeti. Zaustavitev vseh niti omogoča vzporedno učinkovito uporabo vseh virov za dokončanje zbiranja smeti skozi faze označevanja in pometanja. To vodi do zelo visoke stopnje učinkovitosti, ki običajno povzroči visoke ocene na merilih prepustnosti, kot je SPECjbb. Če je pretočnost bistvenega pomena za vašo aplikacijo, je vzporedni pristop odlična izbira.

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