Colectorul de gunoi în C#
Anterior, în tema Tipuri de valori și tipuri de referințe, am analizat diferite tipuri de date și modul în care acestea sunt plasate în memorie. Astfel, când folosim variabile de tip valoare într-o metodă, toate valorile acestor variabile sunt stocate în stivă. După finalizarea execuției metodei, stiva este curățată.
În cazul tipurilor de referință, cum ar fi obiectele claselor, pentru acestea va fi alocat spațiu în stivă, dar acolo va fi stocată doar adresa secțiunii de memorie din heap sau grămadă, unde se află efectiv valorile obiectului respectiv. Și dacă un obiect de tip clasă nu mai este utilizat, odată cu curățarea stivei, referința către secțiunea de memorie este, de asemenea, curățată, însă acest lucru nu duce la o curățare imediată a secțiunii de memorie din heap.
Ulterior, colectorul de gunoi (garbage collector) va observa că nu mai există referințe către secțiunea de memorie respectivă și o va curăța.
De exemplu:
Test();
void Test()
{
Person tom = new Person("Tom");
Console.WriteLine(tom.Name);
}
record class Person(string Name);
În metoda Test este creat un obiect Person. Cu ajutorul operatorului new, în heap este alocată o secțiune de memorie pentru a stoca obiectul. Iar în stivă se adaugă adresa acestei secțiuni de memorie. În metoda Main, definită implicit, apelăm metoda Test. Și după ce metoda Test se încheie, spațiul din stivă este curățat, iar colectorul de gunoi eliberează secțiunea de memorie alocată anterior pentru obiectul Person.
Colectorul de gunoi nu este lansat imediat după ștergerea din stivă a referinței către obiectul plasat în heap. Este lansat atunci când mediul CLR detectează necesitatea, de exemplu, când programul are nevoie de memorie suplimentară.
De regulă, obiectele din heap sunt plasate neordonat, între ele putând exista goluri. Heap-ul este destul de fragmentat. Prin urmare, după curățarea memoriei ca urmare a unei colectări de gunoi, obiectele rămase sunt mutate într-un bloc de memorie continuu. Odată cu aceasta, referințele sunt actualizate pentru a indica corect noile adrese ale obiectelor.
De asemenea, trebuie menționat că pentru obiectele mari există un heap separat - Large Object Heap. În acest heap sunt plasate obiectele a căror dimensiune depășește 85.000 de biți. Particularitatea acestui heap este că în timpul colectării de gunoi nu se efectuează comprimarea memoriei din cauza costurilor mari asociate dimensiunii obiectelor.
Deși comprimarea spațiului ocupat necesită timp și aplicația nu poate continua să ruleze până când colectorul de gunoi nu își termină sarcina, acest proces optimizează, de asemenea, aplicația. Acum, pentru a găsi spațiu liber în heap, mediul CLR nu trebuie să caute insule de spațiu liber între blocurile ocupate. Este suficient să se adreseze pointerului heap-ului, care indică secțiunea de memorie liberă, ceea ce reduce numărul de accesări ale memoriei.
În plus, pentru a reduce costurile asociate cu colectorul de gunoi, toate obiectele din heap sunt împărțite pe generații. Există trei generații de obiecte: 0, 1 și 2.
La generația 0 aparțin obiectele noi, care nu au fost încă supuse colectării de gunoi. La generația 1 aparțin obiectele care au supraviețuit unei colectări, iar la generația 2 - obiectele care au trecut prin mai multe colectări de gunoi.
Când colectorul de gunoi începe să lucreze, acesta analizează mai întâi obiectele din generația 0. Obiectele care rămân relevante după curățare sunt promovate la generația 1.
Dacă, după procesarea obiectelor din generația 0, este încă necesară memorie suplimentară, colectorul de gunoi trece la obiectele din generația 1. Obiectele care nu mai au referințe sunt distruse, iar cele care rămân relevante sunt promovate la generația 2.
Deoarece obiectele din generația 0 sunt mai noi și, adesea, se află în spațiul de memorie apropiat unul de altul, eliminarea lor se face cu costuri minime.
Clasa System.GC.
Funcționalitatea colectorului de gunoi în biblioteca de clase .NET este reprezentată de clasa System.GC. Prin metodele statice, această clasă permite accesul la colectorul de gunoi. De regulă, nu este necesară utilizarea acestei clase. Cel mai frecvent caz de utilizare este colectarea de gunoi în timpul lucrului cu resurse neadministrate, când sunt alocate cantități mari de memorie și este necesară eliberarea lor rapidă.
Să analizăm câteva metode și proprietăți ale clasei System.GC:
- Metoda AddMemoryPressure informează mediul CLR despre alocarea unei cantități mari de memorie neadministrată, care trebuie luată în considerare la planificarea colectării de gunoi. În asociere cu această metodă, se folosește metoda RemoveMemoryPressure, care indică CLR că memoria alocată anterior a fost eliberată și nu mai trebuie luată în considerare la colectarea de gunoi
- Metoda Collect declanșează mecanismul de colectare a gunoiului. Versiunile supraîncărcate ale metodei permit specificarea generației de obiecte până la care trebuie efectuată colectarea de gunoi
- Metoda GetGeneration(Object) permite determinarea generației la care aparține obiectul transmis ca parametru
- Metoda GetTotalMemory returnează cantitatea de memorie în biți care este ocupată în heap-ul administrat
- Metoda WaitForPendingFinalizers suspendă activitatea thread-ului curent până când toate obiectele pentru care se efectuează colectarea de gunoi sunt eliberate
Lucrul cu metodele System.GC este simplu:
// .................................
long totalMemory = GC.GetTotalMemory(false);
GC.Collect();
GC.WaitForPendingFinalizers();
//......................................
Cu ajutorul versiunilor supraîncărcate ale metodei GC.Collect, se poate efectua o configurare mai precisă a colectării de gunoi. De exemplu, versiunea supraîncărcată a acestei metode primește ca parametru un număr - generația până la care trebuie efectuată curățarea. De exemplu, GC.Collect(0) - vor fi eliminate doar obiectele din generația 0.
O altă versiune supraîncărcată primește și un al doilea parametru - o enumerare GCCollectionMode. Această enumerare poate avea trei valori:
- Default: valoare implicită pentru această enumerare (Forced)
- Forced: declanșează imediat execuția colectării de gunoi
- Optimized: permite colectorului de gunoi să determine dacă momentul curent este optim pentru colectarea de gunoi
De exemplu, colectarea imediată a gunoiului până la prima generație de obiecte: GC.Collect(1, GCCollectionMode.Forced);.