A szemétgyűjtés alapjai

A közös nyelvi futtatókörnyezetben (CLR) a szemétgyűjtő (GC) automatikus memóriakezelőként szolgál. A szemétgyűjtő kezeli az alkalmazások memóriafoglalását és felszabadítását. Ezért a felügyelt kóddal dolgozó fejlesztőknek nem kell kódot írniuk a memóriakezelési feladatok elvégzéséhez. Az automatikus memóriakezelés kiküszöbölheti az olyan gyakori problémákat, mint például az objektumok felszabadításának elfelejtése, memóriavesztés vagy a már felszabadított objektumok szabad memóriájának elérése.

Ez a cikk a szemétgyűjtés alapvető fogalmait ismerteti.

Juttatások

A szemétgyűjtő a következő előnyöket biztosítja:

  • A fejlesztőknek nem kell manuálisan felszabadítaniuk a memóriát.

  • Hatékonyan foglalja le az objektumokat a felügyelt halomon.

  • Visszanyeri a már nem használt objektumokat, törli a memóriát, és a memóriát a jövőbeli foglalásokhoz is elérhetővé teszi. A felügyelt objektumok automatikusan tiszta tartalmat kapnak, így a konstruktoroknak nem kell inicializálniuk az összes adatmezőt.

  • Memóriabiztonságot biztosít, ha meggyőződik arról, hogy egy objektum nem használhatja saját magának a másik objektumhoz lefoglalt memóriát.

A memória alapjai

Az alábbi lista a CLR-memória fontos fogalmait foglalja össze:

  • Minden folyamat saját, különálló virtuális címtérrel rendelkezik. Ugyanazon a számítógépen minden folyamat ugyanazzal a fizikai memóriával és lapfájllal rendelkezik, ha van ilyen.

  • Alapértelmezés szerint a 32 bites számítógépeken minden folyamat 2 GB felhasználói módú virtuális címtérrel rendelkezik.

  • Alkalmazásfejlesztőként csak virtuális címtérrel dolgozik, és soha nem manipulálja közvetlenül a fizikai memóriát. A szemétgyűjtő lefoglalja és felszabadítja a virtuális memóriát a felügyelt halomtárban.

    Ha natív kódot ír, a Windows-függvények használatával használhatja a virtuális címteret. Ezek a függvények lefoglalják és felszabadítják a virtuális memóriát natív halommemória esetén.

  • A virtuális memória három állapotban lehet:

    Állapot Leírás
    Ingyenes A memóriablokk nem hivatkozik rá, és kiosztásra is használható.
    Fenntartva A memóriablokk használható, és semmilyen más foglalási kérelemhez nem használható. Azonban nem tárolhat adatokat ebbe a memóriablokkba, amíg le nem véglegesítette.
    Elkövetett A memóriablokk fizikai tárolóhoz van rendelve.
  • A virtuális címtér széttöredezett lehet, ami azt jelenti, hogy vannak szabad blokkok, amelyeket lyukaknak neveznek a címtérben. A virtuális memória lefoglalásának kérése esetén a virtuális memóriakezelőnek egyetlen szabad blokkot kell találnia, amely elég nagy ahhoz, hogy kielégítse a foglalási kérést. Még ha 2 GB szabad területtel is rendelkezik, a 2 GB-ot igénylő foglalás sikertelen lesz, kivéve, ha az összes szabad terület egyetlen címblokkban található.

  • Elfogyhat a memória, ha nincs elegendő virtuális címtér a véglegesítéshez, vagy ha nincs elegendő fizikai hely a véglegesítéshez.

    A lapfájl akkor is használható, ha a fizikai memória terhelése (a fizikai memória iránti igény) alacsony. Amikor először magas a fizikai memóriaterhelés, az operációs rendszernek helyet kell adnia a fizikai memóriában az adatok tárolásához, és biztonsági másolatot készít a fizikai memóriában lévő adatokról az oldalfájlra. Az adatok csak akkor lapozhatók, ha szükség van rá, így lapozást is tapasztalhat olyan helyzetekben, amikor a fizikai memória terhelése alacsony.

Memóriafoglalás

Új folyamat inicializálásakor a futtatókörnyezet egy összefüggő címterületet foglal le a folyamat számára. Ezt a fenntartott címteret felügyelt halomnak nevezzük. A felügyelt halom egy mutatót tart fenn arra a címre, ahol a halom következő objektuma lesz lefoglalva. Ez a mutató kezdetben a felügyelt halom alapcímére van állítva. A rendszer minden referenciatípust lefoglal a felügyelt halomra. Amikor egy alkalmazás létrehozza az első referenciatípust, a rendszer memóriát foglal le a típushoz a felügyelt halom alapcímén. Amikor az alkalmazás létrehozza a következő objektumot, a futtatókörnyezet az első objektumot közvetlenül követő címtérben lefoglalja a memóriát. Mindaddig, amíg a címtér rendelkezésre áll, a futtatókörnyezet továbbra is így foglal le helyet az új objektumok számára.

A memória lefoglalása a felügyelt halomból gyorsabb, mint a nem felügyelt memóriafoglalás. Mivel a futtatókörnyezet úgy foglal le memóriát egy objektum számára, hogy hozzáad egy értéket egy mutatóhoz, majdnem olyan gyors, mint a memória lefoglalása a veremből. Emellett mivel az egymást követő új objektumok egyidejűleg vannak tárolva a felügyelt halomtárban, az alkalmazások gyorsan hozzáférhetnek az objektumokhoz.

Memóriakioldás

A szemétgyűjtő optimalizáló motorja határozza meg a legjobb időt a gyűjtemény végrehajtására a végrehajtott foglalások alapján. Amikor a szemétgyűjtő gyűjtést végez, felszabadítja az alkalmazás által már nem használt objektumok memóriáját. Az alkalmazás gyökerének vizsgálatával meghatározza, hogy mely objektumokat nem használják többé. Az alkalmazások gyökerei közé tartoznak a statikus mezők, a szál veremének helyi változói, a PROCESSZORregisztrációk, a GC-leírók és a véglegesítési üzenetsor. Mindegyik gyökér a felügyelt halom egy objektumára hivatkozik, vagy null értékűre van állítva. A szemétgyűjtő megkérheti a többi futtatókörnyezetet ezekre a gyökerekre. A szemétgyűjtő ezzel a listával hoz létre egy grafikont, amely a gyökerekből elérhető összes objektumot tartalmazza.

A gráfban nem szereplő objektumok nem érhetők el az alkalmazás gyökeréből. A szemétgyűjtő a nem elérhető objektumokat szemétnek tekinti, és felszabadítja a számukra lefoglalt memóriát. A gyűjtés során a szemétgyűjtő megvizsgálja a felügyelt halomhelyet, és megkeresi az elérhetetlen objektumok által elfoglalt címtérblokkokat. Ahogy felderíti az egyes elérhetetlen objektumokat, egy memóriamásoló függvény használatával tömöríti az elérhető objektumokat a memóriában, felszabadítva a nem elérhető objektumokhoz lefoglalt címterek blokkjait. Az elérhető objektumok memóriájának tömörítése után a szemétgyűjtő végrehajtja a szükséges mutatókorrekciókat, hogy az alkalmazás gyökerei az új helyükön lévő objektumokra mutasson. A felügyelt halom mutatóját az utolsó elérhető objektum után is elhelyezi.

A memória csak akkor lesz tömörítve, ha egy gyűjtemény jelentős számú elérhetetlen objektumot észlel. Ha a felügyelt halom összes objektuma túlél egy gyűjteményt, akkor nincs szükség memóriatömörítésre.

A teljesítmény javítása érdekében a futtatókörnyezet külön halomba foglalja a nagy objektumok memóriáját. A szemétgyűjtő automatikusan felszabadítja a nagy objektumok memóriáját. A nagyméretű objektumok memóriabeli áthelyezésének elkerülése érdekében azonban ez a memória általában nem tömöríthető.

Szemétgyűjtés feltételei

A szemétgyűjtés akkor történik, ha az alábbi feltételek egyike teljesül:

  • A rendszer kevés fizikai memóriával rendelkezik. A memória méretét az operációs rendszer alacsony memória-értesítése vagy a gazdagép által jelzett kevés memória észleli.

  • A felügyelt halom lefoglalt objektumai által használt memória túllép egy elfogadható küszöbértéket. Ez a küszöbérték a folyamat futtatásakor folyamatosan módosul.

  • A GC.Collect metódus meghívása. Szinte minden esetben nem kell ezt a módszert meghívnia, mert a szemétgyűjtő folyamatosan fut. Ezt a módszert elsősorban egyedi helyzetekhez és teszteléshez használják.

A felügyelt halom

Miután a CLR inicializálja a szemétgyűjtőt, lefoglal egy memóriaszakaszt az objektumok tárolásához és kezeléséhez. Ezt a memóriát felügyelt halomnak nevezzük, szemben az operációs rendszer natív halomával.

Minden felügyelt folyamathoz van egy felügyelt halom. A folyamat összes szála lefoglalja a memóriát az ugyanazon a halomon lévő objektumok számára.

A memória lefoglalásához a szemétgyűjtő meghívja a Windows VirtualAlloc függvényt, és egyszerre egy memóriaszakaszt foglal le a felügyelt alkalmazások számára. A szemétgyűjtő szükség szerint fenntartja a szegmenseket, és a Windows VirtualFree függvény meghívásával visszaengedi a szegmenseket az operációs rendszerbe (miután törölte őket az objektumokból).

Fontos

A szemétgyűjtő által lefoglalt szegmensek mérete implementációspecifikus, és bármikor változhat, beleértve az időszakos frissítéseket is. Az alkalmazásnak soha nem szabad feltételezéseket feltételeznie egy adott szegmens méretéről vagy attól függenie, és nem is próbálja meg konfigurálni a szegmensfoglalásokhoz rendelkezésre álló memória mennyiségét.

Minél kevesebb objektum van lefoglalva a halomra, annál kevesebb munkát kell elvégeznie a szemétgyűjtőnek. Objektumok lefoglalásakor ne használjon olyan lekerekített értékeket, amelyek túllépik az igényeket, például 32 bájtból álló tömböt oszt ki, ha csak 15 bájtra van szüksége.

A szemétgyűjtés aktiválásakor a szemétgyűjtő visszanyeri a halott objektumok által elfoglalt memóriát. A visszanyerési folyamat úgy tömöríti az élő objektumokat, hogy azok együtt legyenek mozgatva, és a holttér el lesz távolítva, ezáltal a halom kisebb lesz. Ez a folyamat biztosítja, hogy az együtt lefoglalt objektumok együtt maradjanak a felügyelt halomon a helyük megőrzése érdekében.

A szemétgyűjtések tolakodóképessége (gyakorisága és időtartama) a foglalások mennyiségének és a felügyelt halommemória túlélési memóriájának az eredménye.

A halom két halom felhalmozásának tekinthető: a nagy objektumhalom és a kis objektumhalom. A nagyméretű objektum halom 85 000 bájtos és nagyobb objektumokat tartalmaz, amelyek általában tömbök. Ritkán fordul elő, hogy egy példányobjektum extra nagy méretű legyen.

Tipp.

Konfigurálhatja az objektumok küszöbértékének méretét a nagy méretű objektum halomra való ugráshoz.

Generációk

A GC algoritmus több szemponton alapul:

  • Gyorsabban tömörítheti a memóriát a felügyelt halom egy részére, mint a teljes felügyelt halomra.
  • Az újabb objektumok rövidebb élettartamúak, a régebbi objektumok pedig hosszabb élettartamúak.
  • Az újabb objektumok általában egymással kapcsolódnak, és az alkalmazás nagyjából egy időben fér hozzá.

A szemétgyűjtés elsősorban a rövid élettartamú objektumok rekultivációjával történik. A szemétgyűjtő teljesítményének optimalizálása érdekében a felügyelt halom három generációra, 0-ra, 1-re és 2-re van osztva, hogy külön kezelni tudja a hosszú élettartamú és a rövid élettartamú objektumokat. A szemétgyűjtő a 0. generációban tárolja az új objektumokat. Az alkalmazás életének korai szakaszában létrehozott, gyűjteményeket túlélő objektumok előléptetése és tárolása az 1. és a 2. generációban történik. Mivel gyorsabb a felügyelt halom egy részének tömörítése, mint a teljes halom, ez a séma lehetővé teszi, hogy a szemétgyűjtő felszabadítsa a memóriát egy adott generációban ahelyett, hogy a teljes felügyelt halom memóriáját felszabadítja minden alkalommal, amikor gyűjteményt végez.

  • 0. generáció: Ez a generáció a legfiatalabb, és rövid élettartamú objektumokat tartalmaz. Egy rövid élettartamú objektumra példa egy ideiglenes változó. A szemétgyűjtés leggyakrabban ebben a generációban fordul elő.

    Az újonnan lefoglalt objektumok az objektumok új generációját alkotják, és implicit módon 0. generációs gyűjtemények. Ha azonban nagy objektumok, akkor a nagy méretű objektum halomra (LOH) kerülnek, amelyet néha 3. generációnak is neveznek. A 3. generáció egy fizikai generáció, amely logikailag a 2. generáció részeként van összegyűjtve.

    A legtöbb objektum a 0. generációs szemétgyűjtéshez lesz visszanyerve, és nem éli túl a következő generációt.

    Ha egy alkalmazás új objektumot próbál létrehozni, amikor a 0. generáció megtelt, a szemétgyűjtő gyűjtést végez az objektum címterének felszabadításához. A szemétgyűjtő a 0. generációs objektumok vizsgálatával kezdődik, nem pedig a felügyelt halom összes objektumának vizsgálatával. A 0. generációs gyűjtemények gyakran elegendő memóriát igényelnek ahhoz, hogy az alkalmazás továbbra is új objektumokat hozzon létre.

  • 1. generáció: Ez a generáció rövid élettartamú objektumokat tartalmaz, és pufferként szolgál a rövid élettartamú objektumok és a hosszú élettartamú objektumok között.

    Miután a szemétgyűjtő 0. generációs gyűjteményt végez, tömöríti a memóriát az elérhető objektumok számára, és előlépteti őket az 1. generációra. Mivel a gyűjteményeket túlélő objektumok általában hosszabb élettartamúak, érdemes magasabb generációra előléptetni őket. A szemétgyűjtőnek nem kell újrakiadnia a tárgyakat az 1. és a 2. generációban minden alkalommal, amikor a 0. generációs gyűjteményt hajtja végre.

    Ha a 0. generációs gyűjtemény nem igényel elegendő memóriát az alkalmazás számára egy új objektum létrehozásához, a szemétgyűjtő az 1. és a 2. generációs gyűjteményt hajthatja végre. Az 1. generációs objektumok, amelyek túlélik a gyűjteményeket, a 2. generációra kerülnek elő.

  • 2. generáció: Ez a generáció hosszú élettartamú objektumokat tartalmaz. Egy hosszú élettartamú objektumra példa egy kiszolgálóalkalmazás olyan objektuma, amely a folyamat időtartama alatt élő statikus adatokat tartalmaz.

    A 2. generációs objektumok, amelyek túlélnek egy gyűjteményt, a 2. generációban maradnak, amíg el nem döntik, hogy nem lesznek elérhetőek egy jövőbeli gyűjteményben.

    A nagy méretű objektum halomán (amelyet néha 3. generációnak is neveznek) a 2. generációban is gyűjtünk objektumokat.

A szemétgyűjtés meghatározott generációkban fordul elő, ahogy a feltételek indokolják. A generációk gyűjtése azt jelenti, hogy az adott generációban és annak minden fiatalabb generációjában gyűjtünk objektumokat. A 2. generációs szemétgyűjtést teljes szemétgyűjtésnek is nevezik, mivel minden generáció objektumait visszaveszi (vagyis a felügyelt halom összes objektumát).

Túlélés és előléptetések

A szemétgyűjtésben nem visszanyert objektumokat túlélőknek nevezzük, és a következő generációba léptetik elő:

  • A 0. generációs szemétgyűjtést túlélő objektumok az 1. generációra kerülnek elő.
  • Az 1. generációs szemétgyűjtést túlélő objektumok a 2. generációra kerülnek elő.
  • A 2. generációs szemétgyűjtést túlélő objektumok a 2. generációban maradnak.

Amikor a szemétgyűjtő azt észleli, hogy a túlélési arány egy generációban magas, növeli az adott generáció foglalási küszöbértékét. A következő gyűjtemény jelentős mennyiségű visszanyert memóriát kap. A CLR folyamatosan két prioritást egyensúlyoz: nem hagyja, hogy egy alkalmazás munkakészlete túl nagy legyen, ha késlelteti a szemétgyűjtést, és nem engedi, hogy a szemétgyűjtés túl gyakran fusson.

Rövid élettartamú generációk és szegmensek

Mivel a 0. és az 1. generáció objektumai rövid élettartamúak, ezeket a generációkat rövid élettartamú generációknak nevezzük.

A rövid élettartamú generációk a rövid élettartamú szegmensként ismert memóriaszegmensben vannak lefoglalva. A szemétgyűjtő által beszerzett új szegmensek lesznek az új rövid élettartamú szegmensek, és tartalmazzák azokat az objektumokat, amelyek túlélték a 0. generációs szemétgyűjtést. A régi rövid élettartamú szegmens lesz az új 2. generációs szegmens.

A rövid élettartamú szegmens mérete attól függően változik, hogy egy rendszer 32 bites vagy 64 bites, és hogy milyen típusú szemétgyűjtőt futtat (munkaállomás vagy kiszolgáló GC). Az alábbi táblázat a rövid élettartamú szegmens alapértelmezett méretét mutatja be:

Munkaállomás/kiszolgáló GC 32 bites 64 bites
Munkaállomás GC 16 MB 256 MB
Kiszolgálói GC 64 MB 4 GB
Kiszolgálói GC 4 logikai CPU-val > 32 MB 2 GB
Kiszolgálói GC 8 logikai CPU-val > 16 MB 1 GB

A rövid élettartamú szegmens 2. generációs objektumokat tartalmazhat. A 2. generációs objektumok több szegmenst is használhatnak, amennyit a folyamat igényel, és a memória lehetővé teszi.

A rövid élettartamú szemétgyűjtésből felszabadult memória mennyisége a rövid élettartamú szegmens méretére korlátozódik. A felszabadított memória mennyisége arányos a holt objektumok által elfoglalt területtel.

Mi történik a szemétgyűjtés során?

A szemétgyűjtés a következő fázisokkal rendelkezik:

  • Egy jelölési fázis, amely megkeresi és létrehozza az összes élő objektum listáját.

  • Áthelyezési fázis, amely frissíti a tömörítendő objektumokra mutató hivatkozásokat.

  • Egy tömörítési fázis, amely visszanyeri a holt objektumok által elfoglalt helyet, és tömöríti a túlélő objektumokat. A tömörítési fázis áthelyezi azokat az objektumokat, amelyek túlélték a szemétgyűjtést a szegmens régebbi vége felé.

    Mivel a 2. generációs gyűjtemények több szegmenst foglalhatnak el, a 2. generációba előléptetett objektumok áthelyezhetők egy régebbi szegmensbe. Az 1. és a 2. generációs túlélők is áthelyezhetők egy másik szegmensbe, mert a 2. generációsra előléptetik őket.

    Általában a nagy objektum halom (LOH) nincs tömörítve, mert a nagy objektumok másolása teljesítménybeli büntetést von maga után. A .NET Core-ban és a .NET-keretrendszer 4.5.1-ben és újabb verzióiban azonban a GCSettings.LargeObjectHeapCompactionMode tulajdonsággal igény szerint tömörítheti a nagy méretű objektum halomját. Emellett a rendszer automatikusan tömöríti a LOH-t, ha a korlátot a következő beállítással állítja be:

A szemétgyűjtő az alábbi információk alapján állapítja meg, hogy az objektumok élnek-e:

  • Veremgyökerek: Az igény szerinti (JIT) fordító és a stack-lépegető által biztosított veremváltozók. A JIT-optimalizálások meghosszabbíthatják vagy lerövidíthetik a kód azon régióit, amelyeken belül a veremváltozókat a rendszer a szemétgyűjtőnek jelenti.

  • Szemétgyűjtési leírók: Felügyelt objektumokra mutató leírók, amelyek felhasználói kóddal vagy közös nyelvi futtatókörnyezettel foglalhatók le.

  • Statikus adatok: Olyan statikus objektumok az alkalmazástartományokban, amelyek más objektumokra hivatkozhatnak. Minden alkalmazástartomány nyomon követi a statikus objektumait.

A szemétgyűjtés megkezdése előtt a rendszer az összes felügyelt szálat felfüggeszti, kivéve azt a szálat, amely aktiválta a szemétgyűjtést.

Az alábbi ábrán egy olyan szál látható, amely elindít egy szemétgyűjtést, és a többi szálat felfüggeszti:

Screenshot of how a thread triggers a Garbage Collection.

Nem felügyelt erőforrások

Az alkalmazás által létrehozott objektumok többségénél a szemétgyűjtésre támaszkodva automatikusan elvégezheti a szükséges memóriakezelési feladatokat. A nem felügyelt erőforrások azonban explicit törlést igényelnek. A nem felügyelt erőforrások leggyakoribb típusa egy olyan objektum, amely egy operációsrendszer-erőforrást, például egy fájlfogópontot, egy ablakfogópontot vagy egy hálózati kapcsolatot burkol. Bár a szemétgyűjtő nyomon tudja követni egy felügyelt objektum élettartamát, amely egy nem felügyelt erőforrást foglal magában, nem rendelkezik konkrét ismeretekkel az erőforrás megtisztításáról.

Ha olyan objektumot határoz meg, amely nem felügyelt erőforrást foglal magában, ajánlott megadnia a szükséges kódot a nem felügyelt erőforrás nyilvános Dispose metódusban való törléséhez. Egy metódus megadásával Dispose lehetővé teszi, hogy az objektum felhasználói explicit módon felszabadíthassák az erőforrást, amikor befejezték az objektum használatát. Ha olyan objektumot használ, amely egy nem felügyelt erőforrást foglal magában, mindenképpen szükség szerint hívjon meg Dispose .

Meg kell adnia a nem felügyelt erőforrások kiadásának módját is, ha egy öntípusú fogyasztó elfelejt hívást kezdeményezni Dispose. Használhat biztonságos fogópontot a nem felügyelt erőforrás burkolásához, vagy felülbírálhatja a metódust Object.Finalize() .

A nem felügyelt erőforrások tisztításáról további információt a nem felügyelt erőforrások tisztítása című témakörben talál.

Lásd még