JVM optimizacija izvedbe, 3. dio: Skupljanje smeća

Mehanizam prikupljanja smeća na Java platformi uvelike povećava produktivnost programera, ali loše implementirani sakupljač smeća može pretjerano trošiti resurse aplikacija. U ovom trećem članku u seriji optimizacije performansi JVM , Eva Andreasson nudi početnicima s Jave pregled modela memorije i GC mehanizma Java platforme. Zatim objašnjava zašto je fragmentacija (a ne GC) glavni "pogodak!" performansi Java aplikacija i zašto su generacijsko sakupljanje i zbijanje smeća trenutno vodeći (iako ne najinovativniji) pristupi upravljanju fragmentacijom hrpe u Java aplikacijama.

Prikupljanje smeća (GC) postupak je kojim se želi osloboditi zauzeta memorija na koju više ne upućuje nijedan dostupni Java objekt, a važan je dio sustava dinamičkog upravljanja memorijom Java virtualnog stroja (JVM). U tipičnom ciklusu odvoza smeća čuvaju se svi predmeti na koje se još uvijek upućuje i koji su stoga dostupni. Prostor koji zauzimaju prethodno referencirani objekti oslobađa se i vraća da bi se omogućila dodjela novih objekata.

Da biste razumjeli prikupljanje smeća i razne GC pristupe i algoritme, prvo morate znati nekoliko stvari o memorijskom modelu Java platforme.

JVM optimizacija izvedbe: Pročitajte seriju

  • 1. dio: Pregled
  • 2. dio: Sastavljači
  • Dio 3: Skupljanje smeća
  • Dio 4: Istodobno sabijanje GC-a
  • Dio 5: Skalabilnost

Prikupljanje smeća i model memorije Java platforme

Kada odredite opciju pokretanja -Xmxna naredbenom retku vašeg Java programa (na primjer:), java -Xmx:2g MyAppmemorija se dodjeljuje Java procesu. Ova se memorija naziva Java gomila (ili samo hrpa ). Ovo je namjenski adresni prostor memorije u kojem će biti dodijeljeni svi objekti koje je stvorio vaš Java program (ili ponekad JVM). Kako se vaš Java program nastavlja izvoditi i dodjeljivati ​​nove objekte, Java hrpa (što znači da adresni prostor) će se popunjavati.

Na kraju će Java gomila biti puna, što znači da nit koja dodjeljuje ne može pronaći dovoljno velik uzastopni odjeljak slobodne memorije za objekt koji želi dodijeliti. U tom trenutku JVM utvrđuje da se mora odvoz smeća i obavještava sakupljač smeća. Prikupljanje smeća također se može pokrenuti kada Java program pozove System.gc(). KoristećiSystem.gc()ne jamči odvoz smeća. Prije nego što bilo koje prikupljanje smeća može započeti, GC mehanizam prvo će odrediti je li sigurno pokrenuti ga. Sigurno je započeti prikupljanje smeća kada su sve aktivne niti aplikacije na sigurnoj točki da bi se to omogućilo, npr. Jednostavno objašnjeno da bi bilo loše započeti prikupljanje smeća usred tekuće dodjele predmeta ili usred izvršavanje niza optimiziranih CPU uputa (pogledajte moj prethodni članak o kompajlerima), jer biste mogli izgubiti kontekst i time zabrljati krajnje rezultate.

Sakupljač smeća nikada ne bi trebao povratiti objekt na koji se aktivno referira; da bi to učinilo prekršilo bi specifikaciju Java virtualnog stroja. Sakupljač smeća također nije obvezan odmah sakupljati mrtve predmete. Mrtvi se predmeti na kraju sakupljaju tijekom sljedećih ciklusa odvoza smeća. Iako postoji mnogo načina za provođenje sakupljanja smeća, ove dvije pretpostavke vrijede za sve sorte. Pravi je izazov sakupljanja smeća identificirati sve što je uživo (još uvijek referencirano) i povratiti svu nereferenciranu memoriju, ali to učiniti bez utjecaja na pokretanje aplikacija više nego što je potrebno. Sakupljač smeća tako ima dva mandata:

  1. Da biste brzo oslobodili nereferenciranu memoriju kako bi zadovoljili stopu alokacije aplikacije tako da ne ostane bez memorije.
  2. Za povrat memorije uz minimalan utjecaj na performanse (npr. Kašnjenje i propusnost) pokrenute aplikacije.

Dvije vrste odvoza smeća

U prvom članku u ovoj seriji dotaknuo sam se dva glavna pristupa sakupljanju smeća, a to su sabiranje i brojanje referenci. Ovaj put ću se detaljnije upoznati sa svakim pristupom, a zatim predstaviti neke algoritme koji se koriste za implementaciju kolektora za praćenje u proizvodnim okruženjima.

Pročitajte seriju optimizacije izvedbe JVM-a

  • JVM optimizacija izvedbe, 1. dio: Pregled
  • JVM optimizacija izvedbe, 2. dio: Prevoditelji

Sakupljači referenci

Sakupljači brojanja referenci prate koliko referenci pokazuje na svaki Java objekt. Jednom kada broj predmeta postane nula, memorija se može odmah povratiti. Ovaj neposredni pristup obnovljenoj memoriji glavna je prednost pristupa brojanju referenci u odvozu smeća. Vrlo je malo režije kada je u pitanju zadržavanje nereferencirane memorije. Ažuriranje svih referentnih brojeva može biti prilično skupo, međutim.

Glavna poteškoća s kolektorima za brojanje referenci je održavanje točnog broja brojeva referenci. Drugi poznati izazov je složenost povezana s rukovanjem kružnim konstrukcijama. Ako se dva objekta referiraju, a niti jedan živi objekt ne odnosi se na njih, njihova memorija nikada neće biti oslobođena. Oba će predmeta zauvijek ostati s brojem koji nije nula. Povrat memorije povezane s kružnim strukturama zahtijeva veliku analizu, što algoritmu, a time i aplikaciji, donosi skupe troškove.

Traganje sakupljačima

Sakupljači praćenja temelje se na pretpostavci da se svi živi objekti mogu pronaći iterativnim praćenjem svih referenci i naknadnih referenci iz početnog skupa za koje se zna da su živi objekti. Početni skup živih objekata (zvani korijenski objekti ili skraćeno samo korijeni ) nalazi se analizom registara, globalnih polja i okvira stoga u trenutku kada se pokreće prikupljanje smeća. Nakon što je identificiran početni skup uživo, sakupljač praćenja slijedi reference iz tih objekata i stavlja ih u redove da bi se označili kao aktivni, a potom će se njihove reference trasirati. Označavanje svih pronađenih referenciranih objekata uživoznači da se poznati skup uživo povećava s vremenom. Taj se postupak nastavlja sve dok svi referentni (a time i svi živi objekti) ne budu pronađeni i označeni. Nakon što je sakupljač tragova pronašao sve žive objekte, vratit će preostalu memoriju.

Kolektori za praćenje razlikuju se od kolektora za brojanje referenci po tome što mogu rukovati kružnim strukturama. Ulov kod većine sakupljača tragova je faza označavanja koja podrazumijeva čekanje prije nego što se može povratiti nereferencirana memorija.

Sakupljači tragova najčešće se koriste za upravljanje memorijom na dinamičkim jezicima; oni su daleko najčešći za jezik Java i komercijalno su dokazani u proizvodnim okruženjima već dugi niz godina. Usredotočit ću se na traganje sakupljačima do kraja ovog članka, počevši od nekih algoritama koji primjenjuju ovaj pristup prikupljanju smeća.

Algoritmi traganja kolektora

Kopiranje i označavanje i uklanjanje smeća nisu novost, ali i dalje su dva najčešća algoritma koji danas implementiraju praćenje odvoza smeća.

Kolektori za kopiranje

Tradicionalni sakupljači kopija koriste prostor iz svemira i prostor - to jest dva zasebno definirana adresna prostora hrpe. Na mjestu odvoza smeća, živi objekti unutar područja definiranog kao iz svemira kopiraju se u sljedeći dostupan prostor unutar područja definiranog kao prostor. Kada se svi živi objekti iz svemira presele, cijeli se iz svemira može povratiti. Kad raspodjela ponovno započne, započinje s prvog slobodnog mjesta u svemiru.

U starijim implementacijama ovog algoritma prebacuju se iz svemira u prostor, što znači da se, kad je prostor u potpunosti pun, ponovno pokreće sakupljanje smeća i prostor u prostoru postaje prostor iz prostora, kao što je prikazano na slici 1.

Suvremenije implementacije algoritma za kopiranje omogućuju da se proizvoljni adresni prostori unutar hrpe dodijele kao prostor i prostor. U tim slučajevima ne moraju nužno međusobno mijenjati mjesto; nego svaki postaje drugi adresni prostor unutar hrpe.

Jedna od prednosti kolektora za kopiranje je ta što se objekti čvrsto raspoređuju u svemir, potpuno uklanjajući fragmentaciju. Fragmentacija je često pitanje s kojim se bore drugi algoritmi za odvoz smeća; nešto o čemu ću raspravljati kasnije u ovom članku.

Loše strane kolektora za kopiranje

Kolektori za kopiranje obično su sakupljači koji zaustavljaju svijet , što znači da se nijedan rad na aplikaciji ne može izvršiti sve dok je prikupljanje smeća u ciklusu. U implementaciji stop-the-world, što je veće područje koje morate kopirati, to će veći utjecaj na izvedbu vaše aplikacije biti. To je nedostatak za programe koji su osjetljivi na vrijeme odziva. S kolektorom za kopiranje također morate uzeti u obzir najgori mogući scenarij, kada je sve uživo iz svemira. Uvijek morate ostaviti dovoljno prostora za glavu da se objekti uživo mogu premještati, što znači da prostor u svemiru mora biti dovoljno velik da može ugostiti sve u njemu. Algoritam kopiranja malo je neučinkovit u memoriji zbog ovog ograničenja.

Sakupljači oznaka i zamaha

Većina komercijalnih JVM-ova raspoređenih u poslovnim proizvodnim okruženjima pokreću sakupljače oznaka i zamaha (ili označavanja), koji nemaju učinak na učinak koji imaju sakupljači kopiranja. Neki od najpoznatijih sakupljača oznaka su CMS, G1, GenPar i DeterministicGC (vidi Resursi).

A oznaka i brisanje kolektor tragove reference i oznake za svaki je pronađeno objekt sa „živom” malo. Obično postavljeni bit odgovara adresi ili u nekim slučajevima skupu adresa na hrpi. Na primjer, bit uživo može se pohraniti kao bit u zaglavlju objekta, vektor bitova ili mapa bitova.

Nakon što je sve označeno uživo, počet će faza čišćenja. Ako kolektor ima fazu čišćenja, on u osnovi uključuje neki mehanizam za ponovno kretanje hrpom (ne samo skup uživo već i cijela duljina hrpe) kako bi pronašao sve neobilježene komadi uzastopnih memorijskih adresnih prostora. Neoznačena memorija je besplatna i povratna. Sakupljač zatim povezuje te neoznačene dijelove u organizirane besplatne popise. U sakupljaču smeća mogu biti razni besplatni popisi - obično organizirani prema veličinama komada. Neki JVM-ovi (poput JRockit Real Time) implementiraju kolektore s heuristikom koja dinamički prikazuje raspon veličina na temelju podataka profiliranja aplikacija i statistike veličine objekta.

Po završetku faze čišćenja, dodjela će početi ponovno. Nova područja raspodjele dodjeljuju se s besplatnih popisa, a dijelovi memorije mogu se uskladiti s veličinama objekata, prosjecima veličine objekta po ID-u niti ili prilagođenim veličinama TLAB-a. Prilagođavanje slobodnog prostora bliže veličini onoga što vaša aplikacija pokušava dodijeliti optimizira memoriju i moglo bi pomoći u smanjenju fragmentacije.

Više o veličinama TLAB-a

TLAB i TLA (Thread Local Allocation Buffer ili Thread Local Area) particije raspravlja se u JVM optimizaciji performansi, 1. dio.

Loše strane sakupljača oznaka i zamaha

Faza označavanja ovisi o količini podataka uživo na vašoj hrpi, dok faza pomicanja ovisi o veličini hrpe. Budući da za povrat memorije morate pričekati da se završe i faze označavanja i pomicanja , ovaj algoritam izaziva pauze u vremenu većih gomila i većih skupova podataka uživo.

Jedan od načina na koji možete pomoći aplikacijama koje oduzimaju puno memorije je korištenje opcija GC-podešavanja koje odgovaraju različitim scenarijima i potrebama aplikacija. Ugađanje u mnogim slučajevima može barem odgoditi bilo koju od ovih faza da postane rizik za vašu aplikaciju ili ugovore na razini usluge (SLA). (SLA određuje da će aplikacija ispuniti određeno vrijeme odziva aplikacije - tj. Kašnjenje.) Ugađanje za svaku promjenu opterećenja i modifikaciju aplikacije ponavljajući je zadatak, s obzirom da ugađanje vrijedi samo za određeno radno opterećenje i stopu raspodjele.

Implementacije označavanja i pometanja

Postoje barem dva komercijalno dostupna i dokazana pristupa za provedbu sakupljanja oznaka i zamaha. Jedan je paralelni pristup, a drugi paralelni (ili uglavnom istodobni) pristup.

Paralelni kolektori

Paralelno prikupljanje znači da se resursi dodijeljeni procesu koriste paralelno u svrhu odvoza smeća. Većina komercijalno implementiranih paralelnih sakupljača su monolitni sakupljači koji zaustavljaju svijet - sve niti aplikacije zaustavljaju se dok ne završi cijeli ciklus odvoza smeća. Zaustavljanje svih niti omogućuje paralelno učinkovito korištenje svih resursa za završetak odvoza smeća kroz faze označavanja i čišćenja. To dovodi do vrlo visoke razine učinkovitosti, što obično rezultira visokim rezultatima na referentnim vrijednostima protoka kao što je SPECjbb. Ako je protok neophodan za vašu aplikaciju, paralelni pristup izvrstan je izbor.