Ilustrovano: mehanizam sakupljanja smeća u Javi
Sakupljanje smeća jeste mehanizam koji u heap memoriji pronalazi koje se objekti još koriste, a koje ne, pa potom ove druge sakuplja i oslobađa.
Pod objektom u upotrebi podrazumevamo objekat na koji u programu još uvek postoji referenca; pod neiskorišćenim objektom podrazumevamo objekat na koji u programu više nema reference, pa ni memoriju koju zauzima moguće je osloboditi.
Pre nastanka jezika Java, svi su poželeli da pišu programe u C-ili C++-u; tada je postojala velika napetost — jezici poput C++-a pri kreiranju objekta neprestano zahtevaju zauzimanje prostora, a kad im se više ne treba, ponovo oslobađanje prostora; mora da se pišu i konstruktori i destruktori, mnogo vremena se provodi u neprekidnom alociranju, pa beskonačnom uništavanju. Java je drugačija — ona ima sakupljač smeća, pa oslobađanje memorije prepusta sakupljaču.
Prvi korak sakupljanja smeća jeste označavanje. Tada sakupljač smeća pronalazi šta je u memoriji u upotrebi, a šta nije.
[●][○][●][○][○][●][○][●]
● = referenciran objekat (živ)
○ = nereferenciran objekat (smeće)Sakupljač smeća mora da proveri sve objekte da bi znao koji su referencirani, a koji ne. Ako u sistemu treba proveriti apsolutno sve objekte, ovaj korak može biti prilično vremenski zahtevan.
Drugi korak sakupljanja smeća jeste brisanje; ovaj korak uklanja označene nereferencirane objekte.
pre: [●][○][●][○][○][●] ○ se briše
posle: [●][ ][●][ ][ ][●] slobodna mesta se vode u listi slobodne memorijeAlokator memorije čuva reference na slobodnu memoriju kako bi je dodelio novim objektima.
Treći korak sakupljanja smeća jeste sabijanje; radi poboljšanja performansi, nakon brisanja nereferenciranih objekata, preostali referencirani objekti mogu se sabiti (komprimovati) tako da zauzimaju zajednički prostor, čime postaje jednostavnije i brže alokirati nove objekte.
pre: [●][ ][●][ ][ ][●]
posle: [●][●][●][ slobodno ]Već je napomenuto da pojedinačno označavanje i sabijanje svih objekata u Java virtuelnoj mašini veoma je neefikasno: što se više objekata alocira, sakupljanje smeća duže traje. Međutim, statistički gledano, većina objekata zapravo prestaje da se koristi ubrzo nakon nastanka.
Pogledajmo primer. Na klasičnom grafikonu vertikalna osa predstavlja alocirane bajtove, a horizontalna osa vreme rada programa:
bajtovi
▲ ▲▲ ▲ ▲▲
│ ▓▓▓▓ ▓▓ ▓▓▓▓ ← oštri vrhovi: objekti koji uginu odmah po nastanku
│ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓
│ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ← sve tanji pojas: malo objekata koji dugo prežive
└──────────────────────▶ vremeVidimo da preživelih (neoslobođenih) objekata s vremenom rada bude sve manje. Vrh na levoj strani grafa takođe pokazuje da većina objekata zapravo vrlo kratko živi.
Ovde se ovaj tekst deli u dva dela: jedan je objašnjenje stranog autora, a drugi objašnjenje autora iz Alija. Prvo pogledajmo ono strano.
Na osnovu prethodnog pravila, može se poboljšati efikasnost JVM-a. Metod je: heap se podeli na nekoliko delova (tzv. generacija), i to na mladu generaciju, staru generaciju i trajnu generaciju.
Novi objekti alokacijom se smeštaju u mladu generaciju. Čim se memorija mlade generacije napuni, pokreće se takozvani proces manjeg sakupljanja smeća (Minor GC) nad mrtvim objektima. Što je u jednoj oblasti mlade generacije više mrtvih objekata, proces sakupljanja je brži; objekti koji su još živi tada stare i naposletku, dovoljno ostareli, prelaze u staru generaciju.
Događaj „Stop the World” — manje sakupljanje smeća pripada takozvanom događaju „Stop the World”. Pri takvom događaju sve programske niti moraju da se pauziraju, dok se događaj ne završi (ovde: dok se ne završi sav posao sakupljanja).
Stara generacija služi za čuvanje dugovečnih objekata. Obično se postavlja prag; kada se dostigne ta starost, objekti iz mlade generacije premeštaju se u staru. Naposletku i stara generacija biva sakupljena; taj događaj jeste Major GC.
I Major GC pokreće STW (Stop the World). Major GC je obično znatno sporiji, jer obuhvata sve preživele objekte. Zato bi, za aplikacije osetljive na odziv, trebalo izbegavati Major GC. Takođe, trajanje STW-a kod Major GC-a zavisi od tipa sakupljača smeća za staru generaciju.
Trajna generacija sadrži metapodatke koje JVM koristi za opisivanje klasa i metoda u aplikaciji. JVM je popunjava u toku rada, na osnovu klasa koje aplikacija koristi. Osim toga, ovde su smeštene i klase i metode biblioteke Java SE.
Ako JVM ustanovi da neke klase više nisu potrebne, a drugima možda treba prostor, te klase mogu biti sakupljene.
Prvo, svaki novi objekat dodeljuje se prostoru eden. Oblasti oba preživela su prazne.

Kada se prostor eden napuni, pokreće se manje sakupljanje smeća.

Referencirani objekti premeštaju se u prvu oblast preživelih. Pri čišćenju prostora eden brišu se nereferencirani objekti.

Pri sledećem Minor GC, oblast Eden radi isto. Brišu se nereferencirani objekti, a referencirani premeštaju u oblast preživelih. Ovde se, međutim, premeštaju u drugu oblast preživelih (S1).
Pored toga, objekti koji su preživeli u prvoj oblasti preživelih (S0) pri prethodnom Minor GC-u povećavaju starost i premeštaju se u S1. Kada se svi preživeli objekti premeste u S1, i S0 i oblast Eden se prazne. Obrati pažnju da oblast preživelih sada sadrži objekte različite starosti.
pre Minor GC: Eden [novo alocirani] S0 [preživeli iz prošlog GC, age +1] S1 [prazno]
│ kopiraju se samo preživeli
▼
posle Minor GC: Eden [prazno] S0 [prazno] S1 [svi preživeli, različite starosti]Pri sledećem Minor GC, postupak se ponavlja, ali se ovaj put oblasti preživelih menjaju. Referencirani objekti premeštaju se u S0. Preživeli objekti povećavaju starost. Oblast Eden i S1 se prazne.

Ovaj slajd prikazuje promociju. Nakon manjeg GC-a, kada ostareli objekti dostignu određeni prag starosti (u ovom primeru 8), oni iz mlade generacije bivaju unapređeni u staru.

Kako se manji GC neprestano dešava, objekti će nastaviti da bivaju unapređivani u staru generaciju.

Time je gotovo obuhvaćen ceo proces u mladoj generaciji. Naposletku će se GC vršiti uglavnom nad starom generacijom, čišćenjem i konačnim sabijanjem te oblasti.

Sada pogledajmo verziju autora iz Alija.
Java heap (Java Heap) jeste najveća oblast memorije kojom upravlja JVM; pošto je heap glavna oblast pod upravljanjem sakupljača smeća, ovde ćemo analizirati strukturu Java heap-a.
Java heap se uglavnom deli na dve oblasti — mladu i staru generaciju, pri čemu se mlada generacija deli na oblast Eden i oblast preživelih (Survivor), a oblast preživelih još na From i To. Ovde se možda pitate: zašto uopšte postoji oblast preživelih i zašto se ona još deli na dve?
U većini slučajeva objekti se alokacijom smeštaju u oblast Eden mlade generacije. Kada oblast Eden nema dovoljno prostora za alokaciju, virtuelna mašina pokreće Minor GC; Minor GC je učestaliji od Major GC i brže se sakuplja.
Nakon Minor GC, Eden se prazni; većina objekata iz oblasti Eden biva sakupljena, a oni preživeli objekti koji se ne sakupljaju prelaze u oblast From preživelih (ako From ne može da ih primi, idu direktno u Old oblast).
Oblast preživelih služi kao bafer između oblasti Eden i Old oblasti, slično žutom svetlu na semaforu. Survivor se deli na dve oblasti: From i To. Pri svakom Minor GC preživeli objekti iz Edena i From-a stavljaju se u oblast To preživelih (ako To ne može da ih primi, idu direktno u Old oblast).
Razlog postojanja oblasti preživelih jeste to što, bez nje, pri svakom Minor GC u oblasti Eden, preživeli objekti bi išli pravo u staru generaciju, koja bi se brzo napunila. A mnogo objekata, iako preživi jedan Minor GC, zapravo neće dugo poživeti — možda će biti sakupljeni već drugi ili treći put. Tada bi ih smeštanje u staru generaciju bila očito nerazumna odluka.
Zato je smisao postojanja oblasti preživelih da se smanji broj objekata koji odlaze u staru generaciju, a time i broj Major GC-a. Predselekcija oblasti preživelih osigurava da samo objekti koji prežive 16 Minor GC-a u mladoj generaciji odlaze u staru.
Najveća prednost dveju oblasti preživelih jeste rešavanje fragmentacije memorije.
Pretpostavimo šta bi bilo da Survivor ima samo jednu oblast. Posle Minor GC, Eden se isprazni, preživeli objekti se smeste u Survivor, a među objektima koji su već bili u Survivoru možda ima i onih koji treba da budu očišćeni. Problem: kako ih očistiti? U ovom scenariju možemo samo da radimo označavanje-brisanje, a znamo da je najveći problem označavanja-brisanja fragmentacija memorije; u mladoj generaciji, gde objekti često nastaju i nestaju, označavanje-brisanje nužno vodi ozbiljnoj fragmentaciji. Pošto Survivor ima dve oblasti, pri svakom Minor GC preživeli objekti iz Edena i From kopiraju se u To. Pri drugom Minor GC, From i To zamenjuju uloge, pa se preživeli objekti iz Edena i To kopiraju nazad u From, i tako u krug.
Najveća prednost ovog mehanizma jeste da u toku celog procesa uvek jedna Survivor oblast ostaje prazna, a druga, neprazna, nema fragmente. Zašto onda Survivor ne podeliti na još više oblasti? Na primer tri, četiri, pet? Očigledno, daljim deljenjem oblasti Survivor svaki bi deo bio manji, pa lako dolazi do punjenja; dve oblasti Survivor verovatno su, nakon odmeravanja, optimalno rešenje.
Stara generacija zauzima dve trećine prostora heap memorije; nju čisti samo Major GC i svaki GC pokreće „Stop-The-World”. Što je memorija veća, to je i STW duži, pa veća memorija nije uvek i bolja. Pri mehanizmu garancije memorije, objekti koji ne mogu da se smeste idu direktno u staru generaciju; u još nekoliko situacija takođe završavaju u staroj generaciji.
Veliki objekti, odnosno objekti kojima je potrebno mnogo neprekidnog memorijskog prostora; takvi objekti, bez obzira na to što „nastaju ujutro, umiru uveče”, idu direktno u staru generaciju. Ovo se prvenstveno radi da bi se izbeglo obimno kopiranje memorije između oblasti Eden i dve oblasti Survivor.
Dugovečni objekti; virtuelna mašina za svaki objekat definiše brojač starosti objekta (Age). U normalnim okolnostima objekat se stalno pomera između From i To oblasti Survivor; svaki put kad objekat u oblasti Survivor prođe kroz Minor GC, starost mu se poveća za godinu. Kada starost dostigne 15 godina, prebacuje se u staru generaciju. Naravno, JVM podržava i posebno podešavanje ovog praga 15.
Dinamička starost objekata; virtuelna mašina ne zahteva strogo da starost objekta mora dostići 15 pre nego što ode u staru generaciju — ako je zbir veličina svih objekata iste starosti u oblasti Survivor veći od polovine oblasti Survivor, objekti čija je starost veća ili jednaka toj mogu odmah preći u staru generaciju, bez čekanja na „punoletstvo”.
To je zapravo pomalo slično balansiranju opterećenja; „round-robin” je jedna od metoda balansiranja, koja osigurava da svaki server dobije jednako zahteva. Deluje vrlo uravnoteženo, ali hardver i zdravstveno stanje svakog servera se razlikuju, pa možemo algoritam balansiranja prilagoditi i na osnovu broja zahteva koje server primi, ili vremena odziva servera.
