Ez a dokumentum elmagyarázza, mi a szemétgyűjtő a java-ban és a szemétgyűjtők fő típusai az egyes szemétgyűjtők viselkedésével. Kérjük, vegye figyelembe azt is, hogy ebben a cikkben nem magyarázom el, hogyan történik a halomkiosztás és hogyan indulnak el a major/minor szemétgyűjtők. Ezt a következő cikkben fogom elmagyarázni.
A java egy objektumorientált programozási nyelv, amely magában foglalja az automatikus szemétgyűjtést. A Java automatikusan kiosztja és felszabadítja a memóriát, így a programokat nem terheli ez a feladat. Most (Java-12) a Java hétféle szemétgyűjtővel rendelkezik,
- Serial Garbage Collector
- Parallel Garbage Collector
- CMS Garbage Collector
- G1 Garbage Collector
- Epsilon Garbage Collector
.
- Z szemétgyűjtő
- Shenandoah szemétgyűjtő
5. és 6. szemétgyűjtő a java 11-ben, a 7. pedig a java 12-ben került bevezetésre. A legtöbb alkalmazásban a termelésben az első négy típusú szemétgyűjtőt használják. Mivel az utolsó három szemétgyűjtő nemrég került a képbe.
A GC-k miatt kell aggódnunk?
Igen, valóban. Aggódnunk kell a GC és a viselkedése miatt. Mert jelentős teljesítménykülönbséget biztosíthat. Minden GC-nek megvannak a maga előnyei és hátrányai. Fejlesztőként világos elképzelésünk kell, hogy legyen az összes szemétgyűjtő viselkedéséről, és az üzleti forgatókönyvünk alapján kell kiválasztanunk egy szemétgyűjtőt. A szemétgyűjtőt úgy választhatjuk ki, hogy a választást JVM argumentumként adjuk át.
Serial Garbage Collector
Ez a legegyszerűbb GC implementáció. Alapvetően egyszálas környezetre tervezték. Ez a GC implementáció lefutásakor az összes alkalmazásszálat befagyasztja. Egyetlen szálat használ a szemétgyűjtéshez. Ezért nem jó ötlet többszálú alkalmazásokban, például szerverkörnyezetben használni.
A soros szemétgyűjtő engedélyezéséhez a következő argumentumot használhatjuk:
java -XX:+UseSerialGC -jar Application.java
Párhuzamos szemétgyűjtő
A párhuzamos szemétgyűjtőt átviteli gyűjtőnek is nevezik. A soros szemétgyűjtőtől eltérően ez több szálat használ a szemétgyűjtéshez. A soros szemétgyűjtőhöz hasonlóan ez is lefagyasztja az összes alkalmazásszálat a szemétgyűjtés végrehajtása közben. A szemétgyűjtő leginkább azokhoz az alkalmazásokhoz alkalmas, amelyek elviselik az alkalmazásszüneteket.
A párhuzamos szemétgyűjtő engedélyezéséhez a következő argumentumot használhatjuk:
java -XX:+UseParallelGC -jar Application.java
Ha ezt a GC-t használjuk, megadhatjuk a maximális szemétgyűjtő szálakat és a szüneteltetési időt, az átviteli teljesítményt és a lábnyomot (heap méretét)
A szemétgyűjtő szálak számát a parancssori opcióval
-XX:ParallelGCThreads=<N>
A maximális szünetidő cél (két GC közötti különbség)a parancssori opcióval adható meg
-XX:MaxGCPauseMillis=<N>
A maximális áteresztőképességi célt (a szemétgyűjtéssel töltött idő és a szemétgyűjtésen kívül töltött idő viszonylatában mérve) a parancssori opcióval
-XX:GCTimeRatio=<N>
A maximális heap footprint (a program futás közben igényelt heap-memória mennyisége) a -Xmx<N> opcióval adható meg.
CMS szemétgyűjtő
A CMS (Concurrent Mark Sweep) szemétgyűjtő több szemétgyűjtő szálat használ a szemétgyűjtéshez. Átvizsgálja a halom memóriáját, hogy megjelöljön példányokat kilakoltatásra, majd a megjelölt példányokat lesöpri. Olyan alkalmazásokhoz tervezték, amelyek a rövidebb szemétgyűjtési szüneteket részesítik előnyben, és amelyek megengedhetik maguknak, hogy az alkalmazás futása közben a processzor erőforrásokat megosszák a szemétgyűjtővel.
A CMS szemétgyűjtő csak a következő két forgatókönyvben
- A hivatkozott objektumok jelölése során a régi generációs térben tartja az összes alkalmazásszálat.
- A halom memóriájának bármilyen módosítása a szemétgyűjtés elvégzésével párhuzamosan
A párhuzamos szemétgyűjtővel összehasonlítva a CMS gyűjtő több CPU-t használ a jobb alkalmazási átviteli teljesítmény biztosítása érdekében. Ha több CPU-t tudunk kiosztani a jobb teljesítmény érdekében, akkor a CMS szemétgyűjtő a párhuzamos gyűjtővel szemben előnyösebb választás.
A CMS szemétgyűjtő engedélyezéséhez a következő argumentumot használhatjuk:
java -XX:+USeParNewGC -jar Application.java
G1 Garbage Collector
G1 (Garbage First) Garbage Collector a többprocesszoros gépeken futó, nagy memóriaterülettel rendelkező alkalmazásokhoz készült. A JDK7 Update 4 óta és a későbbi kiadásokban is elérhető.
A heap memóriát régiókra osztja, és ezeken belül párhuzamosan végzi a gyűjtést. A G1 a memória visszakövetelése után a szabad heapterületet menet közben is tömöríti. De a CMS garbage collector stop the world (STW) helyzetekben tömöríti a memóriát. A G1 gyűjtő felváltja a CMS gyűjtőt, mivel teljesítményhatékonyabb.
A G1 gyűjtő két fázist tartalmaz;
- Marking
- Sweeping
A többi gyűjtőtől eltérően a G1 gyűjtő a kupacot egyenlő méretű kupac régiókra osztja fel, amelyek mindegyike a virtuális memória összefüggő tartománya. A szemétgyűjtés végrehajtásakor a G1 egyidejűleg globális jelölési fázist mutat be, hogy meghatározza az objektumok életképességét az egész halomban.
A jelölési fázis befejezése után a G1 tudja, hogy mely régiók nagyrészt üresek. Ezeken a területeken gyűjt először, ami általában jelentős mennyiségű szabad helyet eredményez. Ezért nevezik ezt a szemétgyűjtési módszert Garbage-Firstnek.
A G1 szemétgyűjtő engedélyezéséhez a következő argumentumot használhatjuk:
java -XX:+UseG1GC -jar Application.java
Epsilon szemétgyűjtő
Az Epszilon egy nem operatív vagy passzív szemétgyűjtő. Kiosztja a memóriát az alkalmazás számára, de a nem használt objektumokat nem gyűjti össze. Amikor az alkalmazás kimeríti a Java-heapet, a JVM leáll. Ez azt jelenti, hogy az Epsilon szemétgyűjtő lehetővé teszi, hogy az alkalmazások kifogyjanak a memóriából és összeomoljanak.
A szemétgyűjtő célja az alkalmazások teljesítményének mérése és kezelése. Az aktív szemétgyűjtők olyan összetett programok, amelyek a JVM-en belül futnak az alkalmazás mellett. Az Epsilon megszünteti a GC teljesítményre gyakorolt hatását. Nincsenek GC-ciklusok vagy olvasási és írási akadályok. Az Epsilon GC használata esetén a kód elszigetelten fut. Az Epsilon segít láthatóvá tenni, hogy a szemétgyűjtés hogyan befolyásolja az alkalmazás teljesítményét, és mi a memória küszöbérték, mivel megmutatja, mikor fogy el. Példaként ha úgy gondoljuk, hogy csak egy gigabájt memóriára van szükségünk az alkalmazásunkhoz, akkor futtathatjuk -Xmx1g-vel, és láthatjuk a viselkedést. Ha ez a memóriakijelölés nem elegendő, futtassuk le újra a heapdömpinggel. Vegyük figyelembe, hogy engedélyeznünk kell ezt az opciót ahhoz, hogy heap dumpot kapjunk. Ezt az argumentumot használhatjuk arra, hogy heapdumpot kapjunk, ha az alkalmazás a memória elfogyása miatt összeomlik.
XX:HeapDumpOnOutOfMemoryError
Ha minden kis teljesítményt ki kell sajtolnunk az alkalmazásunkból, az Epsilon lehet a legjobb választás a GC számára. De teljesen tisztában kell lennünk azzal, hogy a kódunk hogyan használja a memóriát. Ha szinte egyáltalán nem hoz létre szemetet, vagy pontosan tudjuk, hogy mennyi memóriát használ az adott futási időszakban, akkor az Epsilon egy járható út.
Az Epsilon szemétgyűjtő engedélyezéséhez a következő argumentumot használhatjuk:
java -XX:+UseEpsilonGC -jar Application.java
Z szemétgyűjtő
Az ZGC minden drága munkát egyidejűleg végez, anélkül, hogy 10 ms-nál hosszabb időre leállítaná az alkalmazás szálainak végrehajtását, ami alkalmassá teszi az alacsony késleltetést igénylő és/vagy nagyon nagy halmazt használó alkalmazásokhoz. Az Oracle dokumentációja szerint több terabájtos halmokat is képes kezelni. Az Oracle a Java 11-ben vezette be a ZGC-t. A Z szemétgyűjtő a ciklusait a szálaiban végzi. Az alkalmazást átlagosan 1 ms-ig szünetelteti. A G1 és a Parallel gyűjtők átlagosan nagyjából 200 ms-t.
A Java 12-ben az Oracle teljesítményjavításokat és osztályok kirakodását is hozzáadta, bár a Z még mindig kísérleti státuszban van. Csak 64 bites Linuxon érhető el. De a ZGC kihasználja a 64 bites mutatók előnyeit egy pointer coloring nevű technikával. A színezett mutatók extra információkat tárolnak a halomban lévő objektumokról. Ez az egyik oka annak, hogy csak a 64 bites JVM-re korlátozódik. Ebben a cikkben ezt a forgatókönyvet mélyen kifejtettük (https://www.opsian.com/blog/javas-new-zgc-is-very-exciting/).
A ZGC három fázisban végzi a jelölést.
1. Rövid stop-the-world fázis – Megvizsgálja a GC gyökereit, a heap többi részére mutató lokális változókat. Ezeknek a gyököknek a száma általában minimális, és nem skálázódik a terhelés méretével, így a ZGC szünetei nagyon rövidek, és nem nőnek a halom növekedésével.
2. Egyidejű fázis – Végigjárja az objektumgráfot, és megvizsgálja a színes mutatókat, megjelölve az elérhető objektumokat. A terhelésgátló megakadályozza a GC fázis és bármely alkalmazás tevékenysége közötti versengést.
3. Relokációs fázis – Élő objektumokat mozgat, hogy felszabadítsa a kupac nagy részeit, hogy az allokációk gyorsabbak legyenek. Az áthelyezési fázis megkezdésekor a ZGC oldalakra osztja a heapet, és egyszerre egy-egy oldalon dolgozik. Miután a ZGC befejezte bármelyik gyökér mozgatását, az áthelyezés többi része egy párhuzamos fázisban történik.
A ZGC megpróbálja maga beállítani a szálak számát, és általában igaza is van. De ha a ZGC-nek túl sok szála van, akkor éhezteti az alkalmazást. Ha nincs elég, akkor gyorsabban fogsz szemetet létrehozni, mint ahogy a GC össze tudja gyűjteni. A ZGC fázisai szemléltetik, hogyan kezeli a nagy halmokat anélkül, hogy az alkalmazás memóriájának növekedésével a teljesítményt befolyásolná.
A Z Garbage Collector engedélyezéséhez a következő argumentumot használhatjuk:
java -XX:+UseZGC -jar Application.java
Shenandoah
A Shenandoah egy ultraalacsony szünetidejű szemétgyűjtő, amely csökkenti a GC szünetidőt azáltal, hogy több szemétgyűjtési munkát végez a futó Java programmal egyidejűleg. A CMS és a G1 egyaránt az élő objektumok egyidejű jelölését végzi. A Shenandoah hozzáadja az egyidejű tömörítést.
A Shenandoah memóriarégiókat használ annak kezelésére, hogy mely objektumok nincsenek már használatban, és melyek az élő és tömörítésre kész objektumok. A Shenandoah továbbá minden heap-objektumhoz hozzáad egy továbbítási mutatót, és ezt használja az objektumhoz való hozzáférés ellenőrzésére. A Shenandoah tervezése az egyidejű CPU-ciklusokat és a helyet a szünetidő javulásáért cseréli el. A továbbító mutató megkönnyíti az objektumok mozgatását, de az agresszív mozgatások miatt a Shenandoah több memóriát használ és több párhuzamos munkát igényel, mint más GC-k. De a többletmunkát nagyon rövid világmegállási szünetekkel végzi.
A Shenandoah sok kis fázisban dolgozza fel a kupacot, amelyek többsége párhuzamos az alkalmazással. Ez a kialakítás lehetővé teszi, hogy a GC hatékonyan kezeljen egy nagy halmazt.
- Az első stop-the-world szünet a ciklusban. Előkészíti a kupacot az egyidejű jelölésre, és átvizsgálja a gyökérkészletet. AZGC-hez hasonlóan ennek a szünetnek a hossza a gyökérhalmaz méretének felel meg, nem a halom méretének.
- A következő, párhuzamos fázis bejárja a halmot és azonosítja az elérhető és elérhetetlen objektumokat.
- A harmadik befejezi a jelölési folyamatot a függőben lévő halomfrissítések leürítésével és a gyökérhalmaz újbóli átvizsgálásával. Ez a fázis váltja ki a ciklus második stop-the-world szünetét. A függőben lévő frissítések száma és a gyökérkészlet mérete határozza meg a szünet hosszát.
- Ezután egy másik párhuzamos fázis másolja ki az objektumokat az utolsó jelölési fázisban azonosított régiókból. Ez a folyamat különbözteti meg a Shenandoah-t a többi GC-től, mivel agresszíven tömöríti a kupacot az alkalmazásszálakkal párhuzamosan.
- A következő fázis a ciklus harmadik (és legrövidebb) szünetét váltja ki. Ez biztosítja, hogy az összes GC szál befejezte a kiürítést.
- Amikor ez befejeződik, egy párhuzamos fázis bejárja a heapet, és frissíti a ciklusban korábban áthelyezett objektumok referenciáit.
- A ciklus utolsó megálló szünete a gyökérkészlet frissítésével fejezi be a referenciák frissítését. Ezzel egyidejűleg újrahasznosítja a kiürített régiókat.
- Végül az utolsó fázis visszaköveteli a kiürített régiókat, amelyekben most már nincsenek hivatkozások.
A Shenandoah-t három heurisztika egyikével konfigurálhatjuk. Ezek szabályozzák, hogy a GC mikor kezdi a ciklusait, és hogyan választja ki az evakuálandó régiókat.
1. Adaptív: Figyelemmel kíséri a GC ciklusait, és úgy indítja el a következő ciklust, hogy az még azelőtt befejeződjön, mielőtt az alkalmazás kimerítené a halmazt. Ez a heurisztika az alapértelmezett mód.
2. Statikus: GC-ciklust indít a heap foglaltsága és az allokációs nyomás alapján.
3. Kompakt: Folyamatosan futtatja a GC-ciklusokat. A Shenandoah új ciklust indít, amint az előző befejeződik, vagy az előző ciklus óta kiosztott heap-mennyiség alapján. Ez a heurisztika átviteli többletköltséggel jár, de a legjobb helykihasználást biztosítja.
A Shenandoah-nak gyorsabban kell összegyűjtenie a heapet, mint ahogy az általa kiszolgált alkalmazás kiosztja azt. Ha az allokációs nyomás túl nagy, és nincs elég hely az új allokációkhoz, akkor hiba lép fel. A Shenandoah rendelkezik konfigurálható mechanizmusokkal erre a helyzetre.
- Pacing: Ha a Shenandoah elkezd lemaradni az allokáció ütemétől, akkor a felzárkózás érdekében leállítja az allokációs szálakat. A leállások általában elegendőek az enyhe allokációs kiugrásokhoz. A Shenandoah legfeljebb 10 ms-os késleltetéseket vezet be. Ha a pacing nem sikerül, a Shenandoah a következő lépésre lép: degenerált GC.
- Degenerált GC: Ha allokációs hiba történik, a Shenandoah egy stop-the-world fázist indít. A fázist az aktuális GC-ciklus befejezésére használja. Mivel a stop-the-world nem küzd az alkalmazással az erőforrásokért, a ciklusnak gyorsan be kell fejeződnie, és ki kell küszöbölnie az allokációs hiányt. Gyakran előfordul, hogy egy degenerált ciklus azután következik be, hogy a ciklus munkájának nagy része már befejeződött, így a stop-the-world rövid. A GC-napló azonban teljes szünetként fogja jelenteni.
- Teljes GC: Ha mind a tempózás, mind a degenerált GC sikertelen, a Shenandoah visszatér a teljes GC-ciklushoz. Ez a végső GC garantálja, hogy az alkalmazás nem fog memórián kívüli hibával meghibásodni, kivéve, ha nincs több heap.
A Shenandoah ugyanazokat az előnyöket kínálja, mint a ZGC nagy heapekkel, de több hangolási lehetőséget. Az alkalmazásod jellegétől függően a különböző heurisztikák megfelelőek lehetnek. A szünetidők talán nem olyan rövidek, mint a ZGC-é, de kiszámíthatóbbak.
A Shenandoah szemétgyűjtő engedélyezéséhez a következő argumentumot használhatjuk:
java -XX:+UseShenanodoahC -jar Application.java
Következtetés
A cikk célja az összes szemétgyűjtő összefoglalása. Ezért egyes tartalmi részek a megadott hivatkozásokból kerültek kiragadásra. Világos képet kell kapnunk a szemétgyűjtőkről, hogy kiválaszthassuk az alkalmazásunk felhasználási eseteinek megfelelő optimális szemétgyűjtőt. Az optimális szemétgyűjtő jelentősen javítani fogja alkalmazásunk teljesítményét.
Hivatkozás
- https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html
- https://www.baeldung.com/jvm-garbage-collectors
- https://dzone.com/articles/java-garbage-collection-3
- https://www.opsian.com/blog/javas-new-zgc-is-very-exciting/
- https://openjdk.java.net/projects/shenandoah/
- https://docs.oracle.com/en/java/javase/12/gctuning/z-garbage-collector1.html#GUID-A5A42691-095E-47BA-B6DC-FB4E5FAA43D0
- http://clojure-goes-fast.com/blog/shenandoah-in-production/