Programavimas

JVM efektyvumo optimizavimas. 3 dalis. Šiukšlių surinkimas

„Java“ platformos šiukšlių surinkimo mechanizmas labai padidina kūrėjų produktyvumą, tačiau blogai įgyvendinamas šiukšlių surinkėjas gali per daug sunaudoti programų išteklius. Šiame trečiame straipsnyje JVM našumo optimizavimas Eva Andreasson siūlo „Java“ pradedantiesiems „Java“ platformos atminties modelio ir GC mechanizmo apžvalgą. Tada ji paaiškina, kodėl susiskaldymas (o ne GC) yra pagrindinis „gotcha!“ „Java“ programų našumo ir kodėl kartų atliekų surinkimas ir sutankinimas šiuo metu yra pagrindinis (nors ir ne pats novatoriškiausias) būdas valdyti „Java“ programų krūvos fragmentaciją.

Šiukšlių kolekcija (GC) yra procesas, kurio tikslas - atlaisvinti užimtą atmintį, į kurią nebepasakomas joks pasiekiamas „Java“ objektas, ir yra esminė „Java“ virtualiosios mašinos (JVM) dinaminės atminties valdymo sistemos dalis. Įprastame šiukšlių surinkimo cikle laikomi visi objektai, į kuriuos dar yra nuoroda ir kurie yra pasiekiami. Vieta, kurią užima anksčiau nurodyti objektai, atlaisvinama ir atgaunama, kad būtų galima paskirstyti naujus objektus.

Norėdami suprasti šiukšlių surinkimą ir įvairius GC metodus bei algoritmus, pirmiausia turite žinoti keletą dalykų apie „Java“ platformos atminties modelį.

JVM našumo optimizavimas: perskaitykite seriją

  • 1 dalis: apžvalga
  • 2 dalis. Kompiliatoriai
  • 3 dalis. Šiukšlių surinkimas
  • 4 dalis. GC tankinimas vienu metu
  • 5 dalis. Mastelis

Šiukšlių surinkimas ir „Java“ platformos atminties modelis

Kai nurodote paleidimo parinktį -Xmx „Java“ programos komandinėje eilutėje (pavyzdžiui: „java -Xmx“: 2g „MyApp“) „Java“ procesui priskiriama atmintis. Ši atmintis vadinama Java krūva (arba tiesiog krūva). Tai skirta atminties adreso vieta, kur bus paskirstyti visi jūsų „Java“ programos (arba kartais JVM) sukurti objektai. Kai jūsų „Java“ programa nuolat veikia ir paskirsto naujus objektus, „Java“ kaupas (tai reiškia, kad adreso erdvė) užpildys.

Galų gale „Java“ kaupas bus pilnas, o tai reiškia, kad priskyrimo gija negali rasti pakankamai didelės laisvosios atminties sekcijos iš eilės norimam priskirti objektui. Tuo metu JVM nustato, kad turi būti surenkamos šiukšlės, ir apie tai praneša šiukšlių surinkėjui. Šiukšlių surinkimas taip pat gali būti suaktyvintas, kai paskambina „Java“ programa System.gc (). Naudojant System.gc () negarantuoja šiukšlių surinkimo. Prieš pradedant bet kokį šiukšlių surinkimą, GC mechanizmas pirmiausia nustatys, ar saugu juos pradėti. Pradėti šiukšlių surinkimą yra saugu, kai visos programos aktyvios gijos yra saugioje vietoje, kad tai būtų galima atlikti, pvz. paprasčiausiai paaiškino, kad būtų blogai pradėti šiukšlių rinkimą vykstančio objekto paskirstymo viduryje arba vykdant optimizuotų procesoriaus instrukcijų seką (žr. mano ankstesnį straipsnį apie kompiliatorius), nes galite prarasti kontekstą ir taip sugadinti pabaigą rezultatus.

Šiukšlių surinkėjas turėtų niekada susigrąžinti aktyviai nurodytą objektą; tai padarius būtų pažeista „Java“ virtualiosios mašinos specifikacija. Šiukšlių surinkėjas taip pat neprivalo nedelsdamas surinkti negyvų daiktų. Negyvi daiktai galiausiai surenkami per kitus šiukšlių surinkimo ciklus. Nors yra daug būdų, kaip įgyvendinti šiukšlių surinkimą, šios dvi prielaidos tinka visoms veislėms. Tikrasis šiukšlių surinkimo uždavinys yra nustatyti viską, kas yra gyva (vis dar nurodoma), ir susigrąžinti bet kokią nereikalaujamą atmintį, tačiau tai padaryti nedarant poveikio veikiančioms programoms labiau nei būtina. Taigi šiukšlių surinkėjas turi du įgaliojimus:

  1. Greitai atlaisvinti nereikalaujamą atmintį, kad būtų patenkinta programos paskirstymo norma, kad joje netrūktų atminties.
  2. Norėdami atgauti atmintį, tuo pačiu minimaliai paveikdami veikiančios programos našumą (pvz., Vėlavimą ir pralaidumą).

Dviejų rūšių šiukšlių surinkimas

Pirmajame šios serijos straipsnyje aš paliečiau du pagrindinius šiukšlių surinkimo būdus, tai yra nuorodų skaičiavimas ir surinkėjų sekimas. Šį kartą išsamiau nagrinėsiu kiekvieną metodą, tada pristatysiu keletą algoritmų, naudojamų kolektorių sekimo diegimui gamybos aplinkoje.

Perskaitykite JVM našumo optimizavimo seriją

  • JVM našumo optimizavimas, 1 dalis. Apžvalga
  • JVM efektyvumo optimizavimas. 2 dalis. Kompiliatoriai

Etalonų skaičiavimo kolektoriai

Etalonų skaičiavimo kolektoriai stebėkite, kiek nuorodų rodo į kiekvieną „Java“ objektą. Kai objekto skaičius tampa lygus nuliui, atmintį galima nedelsiant atgauti. Ši tiesioginė prieiga prie atgautos atminties yra pagrindinis nuorodų skaičiavimo metodo privalumas renkant šiukšles. Nereikalaujama labai mažai pridėtinės atminties. Visų atskaitos taškų atnaujinimas gali būti gana brangus.

Pagrindinis atskaitos skaičiavimo surinkėjų sunkumas yra tai, kad etaloniniai skaičiai yra tikslūs. Kitas gerai žinomas iššūkis yra sudėtingumas, susijęs su žiedinių konstrukcijų tvarkymu. Jei du objektai nurodo vienas kitą ir joks gyvas objektas į juos nenurodo, jų atmintis niekada nebus išlaisvinta. Abu objektai amžinai liks be nulio skaičiaus. Norint susigrąžinti su žiedinėmis struktūromis susijusią atmintį, reikia atlikti didelę analizę, kuri algoritmui, taigi ir programai, suteikia brangių pridėtinių išlaidų.

Kolektorių sekimas

Kolektorių sekimas yra pagrįsti prielaida, kad visus gyvus objektus galima rasti kartojant visas nuorodas ir paskesnes nuorodas iš pradinio žinomų esančių objektų rinkinio. Pradinis gyvų objektų rinkinys (vadinamas šakniniai objektai ar tiesiog šaknis trumpiau) yra analizuojant registrus, visuotinius laukus ir rietuvių rėmus tuo metu, kai suveikia šiukšlės. Po to, kai buvo nustatytas pradinis tiesioginis rinkinys, sekimo rinkėjas seka šių objektų nuorodas ir juos įtraukia į eilę, kad jos būtų pažymėtos kaip gyvos, o vėliau bus atsekamos jų nuorodos. Pažymimi visi rasti nuorodos objektai gyventi reiškia, kad žinomas tiesioginis rinkinys laikui bėgant didėja. Šis procesas tęsiasi tol, kol bus rasti ir pažymėti visi nurodyti (taigi ir visi gyvi) objektai. Kolekcionierius suras visus gyvus objektus, jis susigrąžins likusią atmintį.

Kolektoriai nuo atskaitos skaičiuojančių kolektorių skiriasi tuo, kad jie gali valdyti žiedines konstrukcijas. Dauguma atsekamųjų kolekcininkų sugavimai yra žymėjimo etapas, kurį reikia palaukti, kol galėsite susigrąžinti nenurodytą atmintį.

Sekimo rinktuvai dažniausiai naudojami atminties valdymui dinaminėmis kalbomis; jie yra labiausiai paplitę Java kalboje ir daugelį metų buvo komerciškai įrodyti gamybos aplinkoje. Aš sutelksiu dėmesį į surinkėjų paiešką likusioje šio straipsnio dalyje, pradedant kai kuriais algoritmais, įgyvendinančiais šį požiūrį į šiukšlių surinkimą.

Kolektoriaus algoritmų sekimas

Kopijavimas ir pažymėti ir šluoti šiukšlių surinkimas nėra naujiena, tačiau jie vis dar yra du labiausiai paplitę algoritmai, įgyvendinantys šiukšlių surinkimą.

Kolektorių kopijavimas

Tradiciniai kopijavimo kolekcininkai naudoja a iš kosmoso ir a į kosmosą - tai yra dvi atskirai apibrėžtos krūvos adreso vietos. Šiukšlių surinkimo vietoje gyvi objektai srityje, apibrėžtoje kaip iš kosmoso, nukopijuojami į kitą laisvą erdvę toje srityje, kuri apibrėžiama kaip kosmosas. Kai visi gyvi objektai iš kosmoso yra perkelti, galima išieškoti visą iš kosmoso. Kai paskirstymas vėl prasideda, jis prasideda nuo pirmosios laisvos vietos erdvėje.

Senesnėse šio algoritmo versijose iš kosmoso ir į kosmosą perjungiama vieta, o tai reiškia, kad kai erdvė yra pilna, šiukšlių surinkimas vėl suaktyvinamas ir erdvė tampa iš kosmoso, kaip parodyta 1 paveiksle.

Šiuolaikiškesnis kopijavimo algoritmo įgyvendinimas leidžia savavališkas adresų erdves krūvoje priskirti kosmosui ir iš kosmoso. Tokiais atvejais jie nebūtinai turi keisti vietą; veikiau kiekvienas iš jų tampa kita adreso erdve krūvoje.

Vienas kolekcionierių kopijavimo privalumas yra tas, kad objektai yra tvirtai paskirstomi erdvėje, visiškai pašalinant fragmentaciją. Suskaidymas yra dažna problema, su kuria kovoja kiti šiukšlių surinkimo algoritmai; ką aptarsiu vėliau šiame straipsnyje.

Kolektorių kopijavimo minusai

Paprastai kopijuojami kolekcininkai viso pasaulio kolekcininkai, tai reiškia, kad tol, kol vyksta šiukšlių surinkimas, negalima atlikti jokių taikymo darbų. „Stop-the-world“ diegimas: kuo didesnę sritį reikia nukopijuoti, tuo didesnis poveikis bus jūsų programos našumui. Tai yra trūkumas toms programoms, kurios jautriai reaguoja į atsako laiką. Turėdami kopijavimo kolekcionierių, taip pat turite atsižvelgti į blogiausią scenarijų, kai viskas vyksta iš kosmoso. Visada turite palikti pakankamai erdvės gyviems objektams perkelti, o tai reiškia, kad erdvė turi būti pakankamai didelė, kad viską būtų galima laikyti erdvėje. Dėl šio suvaržymo kopijavimo algoritmas yra šiek tiek neveiksmingas atmintyje.

Ženklo ir šlavimo kolekcionieriai

Dauguma komercinių JVM, diegiamų įmonės gamybos aplinkose, naudoja žymėjimo ir šlavimo (arba žymėjimo) rinkiklius, kurie neturi įtakos našumui, kurį daro kopijavimo kolekcininkai. Kai kurie iš žymiausių žymėjimo rinkėjų yra CMS, G1, „GenPar“ ir „DeterministicGC“ (žr. Ištekliai).

A žymeklių rinkėjas seka nuorodas ir pažymi kiekvieną rastą objektą „gyvu“ bitu. Paprastai nustatytas bitas atitinka adresą arba kai kuriais atvejais adresų rinkinį ant krūvos. Pavyzdžiui, gyvas bitas gali būti saugomas kaip bitai objekto antraštėje, bitų vektoriuje ar bitų žemėlapyje.

Po to, kai viskas bus pažymėta gyvai, prasidės šlavimo fazė. Jei kolekcininkas turi šlavimo fazę, iš esmės yra tam tikras mechanizmas, leidžiantis vėl važiuoti su kaupu (ne tik gyvas rinkinys, bet ir visas krūvos ilgis), kad būtų galima rasti visus nepažymėtus vienas po kito einančių atminties adresų tarpų. Nepažymėta atmintis yra nemokama ir ją galima grąžinti. Tada kolekcininkas susieja šiuos nepažymėtus gabalus į organizuotus nemokamus sąrašus. Šiukšlių surinktuve gali būti įvairių nemokamų sąrašų - paprastai jie organizuojami pagal gabalų dydžius. Kai kurie JVM (pvz., „JRockit Real Time“) diegia kolekcionierius su euristika, kuri dinamiškai pateikia sąrašus pagal programos profiliavimo duomenis ir objekto dydžio statistiką.

Kai šlavimo etapas bus baigtas, paskirstymas bus pradėtas iš naujo. Naujos paskirstymo sritys paskirstomos iš laisvų sąrašų, o atminties dalis galima suderinti su objekto dydžiais, objekto dydžio vidurkiais pagal gijos ID arba pagal programą pritaikytų TLAB dydžių. Tinkamiau pritaikius laisvos vietos dydžiui, kurį jūsų programa bando skirti, optimizuojama atmintis ir tai gali padėti sumažinti susiskaidymą.

Daugiau apie TLAB dydžius

TLAB ir TLA (sriegio vietinio paskirstymo buferis arba sriegio vietinis sritis) skaidymas aptariamas JVM našumo optimizavimo 1 dalyje.

Paženklintų kolekcininkų minusai

Pažymėjimo fazė priklauso nuo jūsų kaupiamų duomenų kiekio, o šlavimo fazė - nuo kaupo dydžio. Kadangi jūs turite laukti, kol abu ženklas ir nušluoti etapai yra baigti, norint atgauti atmintį, šis algoritmas sukelia pertraukos laiko iššūkius didesnėms kaupoms ir didesniems tiesioginiams duomenų rinkiniams.

Vienas iš būdų, kaip galite padėti daug atmintį naudojančioms programoms, yra naudoti GC derinimo parinktis, pritaikytas įvairiems programų scenarijams ir poreikiams. Daugeliu atvejų derinimas gali padėti bent jau atidėti bet kurį iš šių etapų ir tapti rizika jūsų programai ar paslaugų lygio susitarimams (SLA). (SLA nurodo, kad programa atitiks tam tikrą programos atsakymo laiką, t. Y. Vėlavimą.) Kiekvieno apkrovos pakeitimo ir programos modifikavimo derinimas yra pasikartojanti užduotis, nes derinimas galioja tik tam tikram darbo krūviui ir paskirstymo greičiui.

Pažymėjimo ir šlavimo diegimas

Yra bent du komerciškai prieinami ir patikrinti metodai, kaip įgyvendinti ženklų ir šlavimo kolekciją. Vienas iš jų yra lygiagretusis metodas, o kitas - lygiagretusis (arba dažniausiai sutampantis) metodas.

Lygiagrečiai kolektoriai

Lygiagretus rinkimas reiškia, kad procesui priskirti ištekliai yra naudojami lygiagrečiai atliekų surinkimui. Dauguma komerciškai įgyvendinamų lygiagrečių kolektorių yra monolitiniai „viso pasaulio“ surinkėjai - visos taikymo gijos sustabdomos, kol baigsis visas šiukšlių surinkimo ciklas. Sustabdžius visas gijas galima efektyviai naudoti visus išteklius lygiagrečiai, kad būtų baigtas šiukšlių surinkimas per žymėjimo ir šlavimo etapus. Tai lemia labai aukštą efektyvumo lygį, paprastai gaunant aukštus pralaidumo etalonų, tokių kaip SPECjbb, balus. Jei pralaidumas yra būtinas jūsų programai, lygiagretus požiūris yra puikus pasirinkimas.