Sunteți pe pagina 1din 21

Garbage Collection - GC

Creare obiecte, gestiune memorie heap, eliberare memorie heap (distrugere obiecte).
Etapele necesare pentru a accesa o resursa:
1. Creare resursa (operatorul new).
2. Initializare memorie pentru a seta starea initiala a obiectului inainte de a o utiliza
(operatiile se realizeaza in constructor).
3. Utilizare resursa prin accesarea membrilor tipului respectiv.
4. Eliberare resursa (eliberarea zonei de memorie ocupata de resursa).

Managementul automat al memoriei incearca sa rezolve problemele legate de accesarea
obiectelor inexistente (bug de programare) precum si mentinerea memoriei heap intr-o
stare consistenta (pierderi de memorie la nivel de aplicatie).
Programtorul este degrevat de sarcina gestionarii memoriei; de momentul cand va trebui
sa elibereze memoria ocupata de un anumit obiect. Programatorul creaza obiectul, il
foloseste si uneori mai scrie si cod pentru eliminarea obiectului din memorie.
Garbage collector nu are informatii suficiente (mai bine zis complete, pentru ca stie o
multime de lucruri din metadata) despre resursa ce reprezinta un anumit tip in memorie.
Pentru ca o resursa sa se poate elibera corect din memorie, dezvoltatorul trebuie sa scrie
cod in metodele Finalize, Dispose, si Close.

Tipurile preconstruite, cum ar fi Int32, Point, Rectangle, String, ArrayList si
SerializationInfo, reprezinta resurse ce nu au nevoie de cod special pentru a fi eliberate
din memorie.

Tipurile ce reprezinta (sau wrap) resurse negestionate (unmanaged) cum ar fi fisiere,
socheturi, mutexuri, bitmap, etc., au nevoie de executia unui cod cand obiectul este
distrus.

Alocare memorie si initializare resurse.

Cerinta principala a CLR-ului este ca toate resursele sa fie alocate intr-o memorie heap
numita managead heap. Din aceasta memorie managed heap, dezvoltatorul nu va elibera
obiectele, acestea vor fi dealocate automat cand aplicatia nu mai are nevoie de ele.
Cand stie managed heap ca un obiect nu mai este necesar in aplicatie si trebuie eliberat?
Exista mai multi algoritmi pentru a realiza acest lucru, fiecare lucrand bine intr-un anumit
mediu.

Cand un proces este initializat, CLR rezerva o zona contigua de memorie (adrese) care
initial nu contine nici un obiect. Aceasta zona (heap) este gestionata cu ajutorul uni
pointer, numit in cazul de fata, NextObjPtr.

Acest pointer indica adresa urmatorului bloc liber in heap, deci unde se va aloca
urmatorul obiect nou creat.

Initial, NextObjPtr contine adresa de inceput a zonei rezervate pentru heap.

Operatorul new este folosit pentru alocarea unui obiect in heap, iar in IL (limbaj
intermediar) ii corespunde instructiunea newobj.
CLR-ul executa urmatorele lucruri cand se creaza un nou obiect:
1. Calculeaza numarul de octeti necesari pentru noul tip si toate tipurile sale de
baza ;
2. fiecare obiect mentine doua campuri speciale (32 biti lungime): un camp ce
mentine un pointer la o tabela de pointeri la metode si un SyncBlockIndex. Pe un
sistem pe 32 biti se mai adauga in plus 8 octeti pentru fiecare obiect, iar pe un
sistem 64-biti se adauga 16 octeti pentru fiecare obiect.
3. Verifica daca exista memorie suficienta in heap. Daca exista memorie, obiectul
este alocat la adresa data de NextObjPtr.
4. Se apeleaza constructorul obiectului (valoarea lui this este egala cu valoarea lui
NextObjPtr) si se obtine adresa unde este memorat obiectul (pointerul this).
5. Se modifica valoarea lui NextObjPtr, astfel incat acesta sa puncteze la urmatoarea
zona de memorie, libera.



Dupa cum se observa din figura, intre obiecte nu exista spatiu de memorie nefolosit. Una
din caracteristicile managed heap este si aceasta de a memora obiectele unul dupa
altul fara zone libere de memorie intre obiecte (zona contigua de obiecte). Din punctul de
vedere al memoriei gestionate (managed heap) aceasta presupune ca spatiul de adrese
de memorare este infinit. Din cauza ca acest lucru nu este posibil, s-a implementat un
mecanism ce tine sub control aceasta presupunere : garbage collector.
In principiu acest mecanism asigura intretinerea zonei heap astfel incat obiectele ce nu
mai sunt necesare aplicatiei sunt eliminate si memoria este compactata.
Ideea generala pentru GC : este apelat in momentul cand se cere alocarea unui nou
obiect in heap managed si nu mai exista loc. In realitate mecanismul este foarte sofisticat
si supus mereu schimbarii. Acesta poate fi implementat ca un fir de executie ce se
executa in background, fir ce are o prioritate scazuta. Prioritatea firului poate creste in
momentele critice, cand este absolut necesara compactarea memoriei. Obiectele din heap
au asociate anumite atribute folosite de algoritmul implementat in GC. De exemplu
obiectele pot impartite in clase (generatii) pentru a face diferenta intre obiectele vechi
si cele noi. De asemenea obiectele pastreaza o stare ce poate indica faptul ca acestea nu
mai sunt necesare (ar fi trebuit sa se execute destructorul) dar ele inca persista in aceasta
zona.

Algoritmul Garbage Collection

GC verifica daca exista in heap obiecte ce nu mai sunt utilizate de aplicatie, iar in caz
afirmativ obiectele sunt eliberate si memoria compactata. In cazul cand nu mai exista
memorie heap disponibila se lanseaza exceptia OutOfMemoryException.

Fiecare aplicatie are o multime de radacini (puncte de intrare) in heap. Un punct de
intrare contine un pointer la un tip referinta.
Acest pointer poate referi un obiect din heap sau poate fi null.
Toate variabilele globale si locale, variabilele statice, de tip referinta sunt considerate
puncte de intrare (radacini) in heap, de asemenea variabilele parametru de tip referinta de
pe stiva unui fir sunt considerate puncte de intrare in heap.
In interiorul unei metode, un registru CPU ce se refera la un obiect de tip referinta, este
considerat punct de intrare.

Compilatorul JIT creaza tabele interne in momentul cand compileaza o metoda (cod IL),
iar punctele de intrare in aceste tabele contin adrese sau registri CPU ce contin puncte de
intrare in heap.

Folosind aceste tabele si alte informatii pentru obiectele din heap, GC poate determina
daca o anumita zona din heap contine un obiect valid sau nu. De asemenea GC are acces
la stiva firului si o poate examina pentru a determina daca exista metode ce fac referire la
obiecte din heap.

GC presupune ca toate obiectele din heap sunt invalide (adica trebuies eliminate). Se
construieste un graf al tuturor punctelor de intrare in heap pentru obiectele valide. Ceea
ce nu se gaseste in acest graf se considera ca sunt obiecte ce trebuiesc eliminate. In
continuare GC parcurge liniar memoria heap si va elimina obiectele ce nu sunt in graf
(algoritmul este foarte simplificat) compactind in acelasi timp memoria heap.
Compactarea memoriei heap de catre GC are ca efect modificarea tabelei ce contine
puncte de intrare in heap, in caz contrar am avea referinte la obiecte ce nu mai sunt la
adresa corecta (daca un obiect contine un pointer la alt obiect se va modifica si valoarea
acestui pointer). In final NextObjPtr va puncta la prima zona de meorie libera (memorie
organizata in mod liniar).

Din punctul de vedere al programatorului, exista anumite avantaje. Acesta nu mai trebuie
sa scrie cod pentru a gestiona timpul de viata al obiectului.

Urmatorul cod arata cum sunt alocate si gestionate obiectele in heap.

class App {
static void Main()
{
// ArrayList object created in heap, a is now a root.
ArrayList a = new ArrayList();

// Create 10000 objects in the heap.

for (Int32 x = 0; x < 10000; x++)
{
a.Add(new Object()); // Object created in heap
}

// Right now, a is a root (on the threads stack). So a is
// reachable and the 10000 objects it refers to are reachable.

Console.WriteLine(a.Length);

// After a.Length returns, a isnt referred to in the code and is no
// longer a root. If another thread were to start a garbage collection
// before the result of a.Length were passed to WriteLine, the 10001
// objects would have their memory reclaimed.

Console.WriteLine("End of method");
}
}

CLR foloseste metadata pentru a determina membrii unui obiect ce se refera la alte
obiecte.

Finalizare

Orice tip ce incapsuleaza (wrap) o resursa negestionata (unmanaged), cum ar fi fisier,
obiect nucleu mutex, socket, etc., trebuie sa suporte finalizarea, adica sa ofere resursei
eliberarea corecta cand aceasta va fi supusa garbage collection (colectata pentru
eliminare).

Pentru aceasta tipul implementeaza o metoda numita Finalize.

Cand GC determina ca un obiect trebuie eliminat din memorie, acesta apeleaza metoda
Finalize a obiectului, daca aceasta exista.

In metoda Finalize vom apela metoda corespunzatoare pentru eliberarea resursei,
pentru fisiere sau obiecte nucleu aceasta metoa poate avea numele CloseHandle.
Pentru tipurile ce folosesc resurse unmanaged este obligatoriu de a defini aceasta
metoda Finalize, in caz contrar resursa nu este eliberata corect din memorie, si vom
avea pierderi de memorie ce vor fi rezolvate de catre SO la terminarea procesului
(vezi ceva asemanator la obiecte nucleu).

Urmatoarul exemplu arata cum ar trebui scris codul din metoda Finalize.

public sealed class OSHandle
{
// This field holds the Win32 handle of the unmanaged resource.

private IntPtr handle;

// This constructor initializes the handle.
public OSHandle(IntPtr handle)
{
this.handle = handle;
}
// When garbage collected, the Finalize method, which
// will close the unmanaged resources handle, is called.

protected override void Finalize()
{
try
{
CloseHandle(handle);
}
catch()
{
Console.WriteLine(Nu putem elibera resursa!!!);
}
finally
{
base.Finalize();
}
}

// Public method returns the value of the wrapped handle

public IntPtr ToHandle()
{
return handle;
}

// Public implicit cast operator returns the value of the wrapped
handle

public static implicit operator IntPtr(OSHandle osHandle)
{
return osHandle.ToHandle();
}

// Private method called to free the unmanaged resource

[System.Runtime.InteropServices.DllImport("Kernel32")]
private extern static Boolean CloseHandle(IntPtr handle);
}

Acest cod nu se compileaza.
Do not override Finalize object.Finalize. Instead provide a destructor.

Explicatii :
Cand obiectul OSHandle este supus procesului de curatare (obiectul in acest moment nu
mai este valid pentru aplicatie, dar pentru GC este inca valid), GC va apela metoda
Finalize a acestui obiect.
In blocul finally se apeleaza metoda de baza Finalize, adica cea din System.Object,
aceasta asigurand ca acest cod se va executa chiar daca a aparut o exceptie (nu si in
CLR !).

Pentru a inlatura eventualele codari eronate in metoda Finalize (netratarea exceptiilor si
neapelarea metodei System.Object.Finalize) s-a definit o sintaxa speciala pentru aceasta
metoda.

Sa urmarim codul urmator care este aproape identic cu cel anterior.

public sealed class OSHandle
{
// This field holds the Win32 handle of the unmanaged resource.
private IntPtr handle;

// This constructor initializes the handle.

public OSHandle(IntPtr handle)
{
this.handle = handle;
}

// When garbage collected, the destructor (Finalize) method,
which
// will close the unmanaged resources handle, is called.

~OSHandle()
{
CloseHandle(handle);
}

// Public method returns the value of the wrapped handle

public IntPtr ToHandle()
{
return handle;
}

// Public implicit cast operator returns the value of the wrapped
handle

public static implicit operator IntPtr(OSHandle osHandle)
{
return osHandle.ToHandle();
}

// Private method called to free the unmanaged resource

[System.Runtime.InteropServices.DllImport("Kernel32")]
private extern static Boolean CloseHandle(IntPtr handle);
}

Observatii
Aici apare o confuzie in utilizarea acestui destructor pentru OSHandle.
Daca se examineaza codul IL, atunci acesta apare ca cel descris in primul exemplu.

Pentru a crea o instanta a obiectului OSHandle, vom apela o functie Win32 ce returneaza
un handle la o resursa unmanaged, de ex. CreateFile, CreateMutex, CreateSemaphore,
CreateEvent, etc.
Apoi folosim operatorul new din C# pentru a construi o instanta pentru OSHandle, pasind
ca parametru in ctor acest handle.
Cand acest obiect va fi tratat de GC, GC va vedea ca exista o metoda Finalize
implementata si o va apela, ceea ce va permite eliberarea resursei nucleu (vezi si
gestionarea timpului de viata al unui obiect nucleu, se va apela CloseHandle()).
CLR nu suporta (inca) distrugerea determinista a unui obiect (nu putem scrie cod pentru a
distruge obiectul). Finalize va fi apelata numai de GC, iar cand GC se executa nu stim si
nu putem programa acest lucru.

Observatie
Nu se recomanda folosirea metodei Finalize cand se dezvolta un tip. Timpul procesor
pentru GC va creste caci trebuie sa apeleze Finalize pe obiectele pe care le va distruge si
pe toate obiectele la care face referire un alt obiect ce are metoda Finalize implementata.
De exemplu, pentru un tablou cu 10000 de obiecte pe fiecare obiect se va apela metoda
Finalize cand acesta va fi in atentia GC.

Pentru resursele unmanaged este obligatorie folosirea metodei Finalize. Intern se
construieste o lista cu obiectele ce implementeaza metoda Finalize. GC va trebui sa
consulte si aceata lista.

Nu trebuie scris cod in Finalize ce acceseaza obiecte interne ale obiectului curent,
membrii pentru asemenea obiecte.
De exemplu avem un obiect, numit Extern, ce contine un pointer la un alt obiect, numit
Intern. Deoarece GC nu apeleaza intr-o ordine determinista metoda Finalize, este posibil
sa se apeleze Finalize pe obiectul Intern in timp ce obiectul extern este in viata . Nu
avem nici un control asupra ordinii executiei metodelor Finalize.
In acest moment pointerul din obiectul Extern la obiectul Intern este invalid.

Codul din Finalize trebuie sa se execute foarte rapid (asemanator ca la sectiuni critice).

Nu trebuie scris cod pentru sincronizare in cadrul acestei metode.

In Finalize trebuiesc tratate exceptiile.

In Finalize nu trebuie sa facem referire la obiecte managed sau metode statice managed,
este posibil ca aceste obiecte sa nu mai existe sau metodele statice sa faca referire la
obiecte ce nu mai exista.

Urmatorul exemplu emite un sunet cand GC executa o colectie.

public sealed class GCBeep
{
~GCBeep()
{

// Were being finalized, beep.

MessageBeep(-1);

// If the AppDomain isnt unloading, create a new object
// that will get finalized at the next collection.
// Ill discuss IsFinalizingForUnload in the next section.

if (!AppDomain.CurrentDomain.IsFinalizingForUnload())
new GCBeep();
}

[System.Runtime.InteropServices.DllImport("User32.dll")]
private extern static Boolean MessageBeep(Int32 uType);
}

class App
{
static void Main()
{
// Constructing a single GCBeep object causes a beep to
// occur every time a garbage collection starts.

new GCBeep();

// Construct a lot of 100-byte objects.

for (Int32 x = 0; x < 10000; x++)
{
Console.WriteLine(x);
Byte[] b = new Byte[100];
}
}
}


Metoda Finalize va fi apelata chiar daca ctor instantei tipului arunca o exceptie. Deci
metoda Finalize nu trebuie sa presupuna ca obiectul este intr-o stare buna, consistenta.

Exemplu

class TempFile
{
String filename = null;
public FileStream fs;
public TempFile(String filename)
{
// The following line might throw an exception.

fs = new FileStream(filename, FileMode.Create);

// Save the name of this file.
this.filename = filename;
}
~TempFile()
{
// The right thing to do here is to test filename
// against null because you cant be sure that
// filename was initialized in the constructor.

if (filename != null)
File.Delete(filename);
}
}

Acelasi exemplu tratat altfel.

class TempFile
{
String filename;
public FileStream fs;
public TempFile(String filename)
{
try
{
// The following line might throw an exception.
fs = new FileStream(filename, FileMode.Create);
// Save the name of this file.
this.filename = filename;
}
catch
{
// If anything goes wrong, tell the garbage collector
// not to call the Finalize method.

GC.SuppressFinalize(this);

// Let the caller know something failed.

throw;
}
}

~TempFile()
{
// No if statement is necessary now because this code
// executes only if the constructor ran successfully.

File.Delete(filename);
}

}

Cauzele ce produc apelul metodei Finalize

1. Generatia 0 este plina. Acest eveniment este calea cea mai obisnuita de a apela
metoda Finalize, pentru ca acesta apare cand aplicatia se executa si are nevoie sa
aloce noi obiecte.
2. Codul apeleaza explicit metoda statica Collect din System.GC. Codul poate cere
in mod explicit CLR-ului sa execute o colectie. MS nu recomanda apelul acestei
metode.
3. CLR descarca un AppDomain. Cand se intimpla acest lucru, CLR considera ca nu
exista intrari in heap in cadrul AppDomain si apeleaza Finalize pentru obiectele
ce au fost create in AppDomain.
4. CLR se inchide (shuting down). Cand un proces se termina OK, se incearca
terminarea corecta a CLR-ului. In acest moment CLR considera ca nu exista
puncte de intrare in heap in cadrul procesului si apeleaza metoda Finalize pentru
obiectele din heap managed.

CLR foloseste un fir dedicat pentru a apela metodele Finalize. Pentru primele trei
evenimente metoda Finalize intra intr-o bucla infinita, firul este blocat si nu se mai
apeleaza nici o metoda Finalize. Este un dezastru.

Pentru evenimentul 4, fiecare metoda Finalize consuma aproximativ 2 secunde pentru
executie. Daca nu se intimpla asa, CLR omoara procesul si nu se mai apeleaza alte
metode Finalize. De asemenea, daca CLR consuma mai mult de 40 secunde pentru a
apela o metoda Finalize a unui obiect, CLR omoara procesul.
Valorile scrie mai sus pot fi schimbate de MS in cadrul procesului de imbunatatire al
algoritmului pentru GC.

Codul din Finalize poate construi alte obiecte; daca se intimpla asta in timp ce CLR se
termina (shutdown) CLR va continua sa colecteze obiecte si sa apeleze metodele lor
Finalize pina cand nu mai exista nici un obiect sau au trecut 40 de secunde.

Reluam exemplul GCBeep.

Daca un obiect GCBeep este pe cale sa se finalizeze din cauza evenimentului 1 sau 2, un
nou obiect GCBeep este construit. Este corect pentru ca aplicatia continua sa ruleze,
presupunind ca mai multe colectii vor fi in viitor.
Daca un obiect GCBeep este pe cale sa se finalizeze din cauza evenimentului 3 sau 4, un
nou obiect nu va putea fi construit pentru ca acest obiect ar trebui sa fie creat in timp ce
se descarca AppDomain sau CLR a fost oprit. Daca aceste noi obiecte au fost create,
CLR ar trebui sa poata apela Finalize pentru aceste obiecte.
Pentru a preveni constructia acestor noi obiecte GCBeep, metoda Finalize din GCBeep
apeleaza metoda AppDomain.IsFinalizingForUnload. Aceasta metoda returneaza TRUE
daca metoda Finalize a obiectului este apelata pentru ca AppDomain se descarca din
memorie. Solutia de mai sus este buna numai pentru AppDomain.
Pentru CLR MS a adaugat proprietatea read-only HasShutdownStarted la clasa
System.Environment.

Metoda Finalize din GCBeep ar trebui sa citeasca aceasta proprietate si daca este TRUE
sa nu mai permita crearea de noi obiecte GCBeep.
Proprietatea este inaccesibila, pentru ca a fost implementata eronat, a fost implementata
ca o proprietate ce tine de instanta clasei, iar clasa Environment are un ctor privat si deci
nu putem construi obiecte din aceasta clasa.
Pina la modificarea bibliotecii FCL nu exista o cale de a sti daca CLR este pe cale de
terminare sau nu.

Finalizare probleme interne

Sa ne reamintim ca obiectele ce au implementata metoda Finalize sunt pastrate intr-o lista
separata, lista de finalizare, gestionata de GC. Obiectele ce nu implementeaza propria
metoda Finalize nu sunt trecute in lista de finalizare, chiar daca sunt derivate din
System.Object ce are metoda Finalize.

Sunt doua actiuni (operatii) importante ce trebuiesc facute : alocare memorie pentru
obiectul creat si inscrierea obiectului in aceasta lista. In fapt putem considera ca operatie
elementara o operatie care la rindul ei este critica. Putem distinge astfel de operatii:
alocare memorie, completare lista finalizare, construire obiect (apelare constructor).
Ordinea acestor operatii (conform documentatiei MS) este : alocare memorie, completare
lista finalizare, apelare constructor. Fiecare din aceste operatii poate sa esueze. Pentru
fiecare situatie in parte trebuiesc luate masuri speciale in scrierea codului.
Daca obiectul nu este construit din diverse motive (deci ultima operatie), atunci sistemul
trebuie sa fie capabil sa faca rollback pe operatiile anterioare. Cel mai important este
eliminarea obiectului din lista de finalizare. In rest ramane treaba GC sa rezolve problema
cu memoria alocata si nefolosita.



Sa consideram exemplul din figura de mai sus.

Cand se executa GC, obiectele B, E, G, H, I, si J nu mai constituie puncte valide de
intrare in heap (radacini) si deci vor fi supuse procesului de colectare si eliminare.
Pentru fiecare obiect se cauta daca exista in lista de finalizare, iar in caz afirmativ este
eliminata intrarea din aceasta lista si trecuta intr-o alta lista F-reachable si asta inseamna
ca pentru obiectele de aici se va apela metoda Finalize.

Dupa colectie managed heap arata astfel :




Un fir dedicat din CLR, va apela metodele Finalize.
Un fir dedicat este folosit pentru a preveni eventualele probleme legate de sincronizare.
Cand coada F este vida, acest fir este in asteptare. El va fi lansat numai pe evenimente ce
spun ca sunt obiecte in coada F.
Obiectele ce au intrari in coada F nu sunt supuse procesului de eliminare, ele constituie
inca radacini valide pentru heap managed.

Memoria heap managed dupa etapa a doua a colectarii si eliminarii arata astfel:



Paternul Dispose: Fortarea unui obiect sa se stearga

Metoda Finalize se foloseste in special pentru resurse unmanaged.
Metoda nu poate fi apelata direct.

Tipurile ce ofera posibilitatea de a fi in mod determinist dispose sau inchise
implementeaza ceea ce se numeste dispose pattern.
Dispose pattern defineste conventiile la care un dezvoltator ar trebui sa adere in
momentul cand defineste un tip ce doreste sa poata fi sters in mod explicit.
Pentru un tip ce implementeaza dispose pattern, utilizatorul va putea elimina obiectul
exact atunci cand nu mai are nevoie de el.
Un tip poate contine atat Finalize cat si Dispose.

Exemplu : clasa System.IO.BinaryWriter intra in aceasta categorie.

Exemplul urmator descrie o versiune mai buna pentru tipul OSHandle.

using System;
// Implementing the IDisposable interface signals users of
// this class that it offers the dispose pattern.

public sealed class OSHandle : IDisposable
{
// This field holds the Win32 handle of the unmanaged resource.
private IntPtr handle;

// This constructor initializes the handle.

public OSHandle(IntPtr handle)
{
this.handle = handle;
}

// When garbage collected, this Finalize method, which
// will close the unmanaged resources handle, is called.
~OSHandle()
{
Dispose(false);
}
// This public method can be called to deterministically
// close the unmanaged resources handle.

public void Dispose()
{
// Because the object is explicitly cleaned up, stop the
// garbage collector from calling the Finalize method.
GC.SuppressFinalize(this);

// Call the method that actually does the cleanup.
Dispose(true);
}

// This public method can be called instead of Dispose.
public void Close()
{
Dispose();
}

// The common method that does the actual cleanup.
// Finalize, Dispose, and Close call this method.
// Because this class is sealed, this method is private.
// If this class werent sealed, this method wouldnt be protected.

private void Dispose(Boolean disposing)
{
// Synchronize threads calling Dispose/Close
// simultaneously.
lock (this)
{
if (disposing)
{
// The object is being explicitly disposed of/closed,
not
// finalized. It is therefore safe for code in this
if
// statement to access fields that reference other
// objects because the Finalize method of these other
objects
// hasnt yet been called.

// For the OSHandle class, there is nothing to do in
here.
}
// The object is being disposed of/closed or finalized.
if (IsValid)
{
// If the handle is valid, close the unmanaged
resource.
// NOTE: Replace CloseHandle with whatever function
is
// necessary to close/free your unmanaged resource.

CloseHandle(handle);

// Set the handle field to some sentinel value.
// This precaution prevents the possibility
// of calling CloseHandle twice.

handle = InvalidHandle;
}
}
}

// Public property to return the value of an invalid handle.
// NOTE: Make this property return an invalid value for
// whatever unmanaged resource youre using.

public IntPtr InvalidHandle
{
get { return IntPtr.Zero; }
}

// Public method to return the value of the wrapped handle
public IntPtr ToHandle() { return handle; }

// Public implicit cast operator returns the value of the wrapped
handle
public static implicit operator IntPtr(OSHandle osHandle)
{
return osHandle.ToHandle();
}

// Public properties to return whether the wrapped handle is
valid.
public Boolean IsValid
{
get { return (handle != InvalidHandle); }
}

public Boolean IsInvalid
{
get { return !IsValid; }
}

// Private method called to free the unmanaged resource.
[System.Runtime.InteropServices.DllImport("Kernel32")]
private extern static Boolean CloseHandle(IntPtr handle);
}

Cam mult cod, dar acest cod se repeta pentru fiecare tip ce foloseste o resursa
unmanaged.

Explicatii

OSHandle implementeaza interfata System.IDisposable.
In FCL aceasta este definita astfel:

public interface IDisposable
{
void Dispose();
}

Pentru ca un tip sa adere la dispose pattern trebuie sa implementeze aceasta interfata.
Asta inseamna ca tipul ofera o metoda publica, fara parametri, Dispose, ce poate fi
apelata pentru a elibera resursa wrapped de catre obiect. Memoria din heap managed
pentru obiect va fi eliberata de GC.

OSHandle implementeaza o metoda publica Close, ce apeleaza Dispose. Patternul
dispose nu obliga folosirea acestei metode.

Metodele fara parametri, Dispose si Close trebuie sa fie publice si nevirtuale. Metoda
Dispose este la baza virtuala fiind dintr-o interfata. In tipul nostru se defineste ca fiind
public si sealed (nu poate fi folosita in derivare). Compilatorul face implicit asta pentru
noi.
Metoda Dispose fara parametri va apela metoda Dispose cu un parametru de tip bool, iar
codul pentru curatare se va gasi in aceasta ultima metoda. In cadrul metodei Dispose are
loc o sincronizare.




Cand se apeleaza metoda Finalize a unui obiect, valoarea parametrului metodei Dispose
este false.
Asta inseamna ca metoda Dispose nu ar trebui sa execute cod ce face referire la alte
obiecte managed.

Daca clasa OSHandle nu ar fi fost declara sealed, metoda Dispose(bool) ar fi trebuit sa fie
implementata ca protected virtual si nu ca private nonvirtual. Orice clasa ce deriva din
OSHandle ar trebui sa implementeze metoda Dispose(boolean) nu si metodele fara
parametri Dispose si Close si nu trebuie sa implementeze Finalize.

Exemplu de clasa derivata din OSHandle

class SomeType : OSHandle
{
// This field holds the Win32 handle of the unmanaged resource.
private IntPtr handle;
protected override void Dispose(Boolean disposing)
{
// Synchronize threads calling Dispose/Close simultaneously.
lock (this)
{
try
{
if (disposing)
{
// The object is being explicitly disposed of/closed, not
// finalized. It is therefore safe for code in this if
// statement to access fields that reference other
// objects because the Finalize method of these other
// objects hasnt been called.
// For this class, there is nothing to do in here.
}
// The object is being disposed of/closed or finalized.
if (IsValid)
{
// If the handle is valid, close the unmanaged resource.
// NOTE: Replace CloseHandle with whatever function is
// necessary to close/free your unmanaged resource.
CloseHandle(handle);

// Set the handle field to some sentinel value. This
precaution
// prevents the possibility of calling CloseHandle twice.

handle = InvalidHandle;
}
}
finally
{
// Let the base class do its cleanup.
base.Dispose(disposing);
}
}
}
}

Folosirea unui tip ce implementeaza patternul Dispose

Vom explica acest lucru pe baza unui exemplu, folosind clasa System.IO.FileStream.
Aceasta clasa ofera posibilitatea de a lucra cu fisiere.
Intern aceasta clasa pastreaza un handler, cu acces private, la fisierul cu care lucreaza,
handler returnat de functia CreateFile.

Vom crea un fisier, in care se scrie ceva si apoi se sterge.

using System;
using System.IO;
class App
{
static void Main()
{
// Create the bytes to write to the temporary file.
Byte[] bytesToWrite = new Byte[] { 1, 2, 3, 4, 5 };

// Create the temporary file.
FileStream fs = new FileStream("Temp.dat",
FileMode.Create);

// Write the bytes to the temporary file.
fs.Write(bytesToWrite, 0, bytesToWrite.Length);

// Delete the temporary file.
File.Delete("Temp.dat"); // Throws an IOException
}
}

Dupa executie obtinem urmatoarea exceptie:

Unhandled Exception: System.IO.IOException: The process cannot access the file "
Temp.dat" because it is being used by another process.
at System.IO.__Error.WinIOError(Int32 errorCode, String str)
at System.IO.File.Delete(String path)
at App.Main() in c:\Documents and Settings\info\My Documents\SharpDevelop Pro
jects\p6_gc3\Main.cs:line 25
Press any key to continue . . .


Problema apare in metoda Delete.
Exista si sanse ca acest cod sa functioneze. Daca alt fir cauzeaza un GC dupa apelul lui
Write si inainte de Delete, obiectul FileStrem va apela metoda Finalize si fisierul va fi
inchis.
Implementarea patternului Dispose in aceasta clasa inlatura acest bug.

Codul in noua varianta poate fi:

using System;
using System.IO;
class App
{
static void Main()
{
// Create the bytes to write to the temporary file.
Byte[] bytesToWrite = new Byte[] { 1, 2, 3, 4, 5 };

// Create the temporary file.
FileStream fs = new FileStream("Temp.dat",
FileMode.Create);

// Write the bytes to the temporary file.
fs.Write(bytesToWrite, 0, bytesToWrite.Length);

// Explicitly close the file when done writing to it.
((IDisposable) fs).Dispose();

// Delete the temporary file.
File.Delete("Temp.dat"); // This always works now.
}
}

Diferenta este in apelul:

((IDisposable) fs).Dispose();

care inchide fisierul.

Acelasi exemplu, dar inchiderea fisierului se face cu o metoda din clasa FileStream.

using System;
using System.IO;
class App
{
static void Main()
{
// Create the bytes to write to the temporary file.
Byte[] bytesToWrite = new Byte[] { 1, 2, 3, 4, 5 };

// Create the temporary file.
FileStream fs = new FileStream("Temp.dat",
FileMode.Create);

// Write the bytes to the temporary file.
fs.Write(bytesToWrite, 0, bytesToWrite.Length);

// Explicitly close the file when done writing to it.
fs.Close();

// Delete the temporary file.
File.Delete("Temp.dat"); // This always works now.
}
}

Important
Cand definim propriul tip ce implementeaza patternul Dispose, trebuie sa scriem cod in
toate metodele noastre pentru a lansa execeptia System.ObjectDisposedException daca
obiectul a fost eliminat in mod explicit.
Metodele Dispose si Close nu trebuie sa arunce niciodata aceasta exceptie.

Folosirea instructiunii using

Exemplu

using System;
using System.IO;
class App
{
static void Main()
{
// Create the bytes to write to the temporary file.
Byte[] bytesToWrite = new Byte[] { 1, 2, 3, 4, 5 };
// Create the temporary file.

using (FileStream fs =
new FileStream("Temp.dat", FileMode.Create))
{
// Write the bytes to the temporary file.
fs.Write(bytesToWrite, 0, bytesToWrite.Length);
}

// Delete the temporary file.
File.Delete("Temp.dat"); // This always works now.
}
}

In using se initializeaza obiectul si se salveaza o referinta la acesta intr-o variabila. Cu
aceasta referinta apelam metode din obiect.
La intilnirea instructiunii using, compilatorul construieste blocurile try si finally, iar in
blocul finally se face cast la IDisposable pentru a apela metoda Dispose.
Instructiunea using lucreaza numai cu tipuri ce implementeaza interfata IDisposable.

Generatii

Un GC generational (??) face urmatoarele presupuneri:
1. Un obiect este cu atit mai nou cu cit timpul sau de viata este mai mic.
2. Un obiect este cu atit mai vechi cu cit timpul sau de viata este mai mare.
3. Colectarea unei portiuni din heap este mai rapida decat parcurgerea intregului.

La initializare mh (managed heap) nu contine obiecte. Obiectele adaugate se spun ca sunt
din generatia 0. La initializare, CLR stabileste marimea zonei pentru generatia 0 (pp 256
KB).



Dupa un timp obiectele C si E nu mai sunt radacini.

Cand marimea acestei zonei satbilita de CLR este depasita se lanseaza procesul de
colectare.

Obiectele ce au fost tratate odata de GC trec in generatia urmatoare. Fiecarei generatii i se
stabileste o marime in heap. De la generatie la generatie aceste marimi pot sa difere (cf
doc. MS). Noile obiecte ce se creaza pleaca cu generatia 0.


La o noua colectare generatia 1 va deveni 2, 0 va deveni 1.
Se presupune ca obiectele cu generatia mare nu au nevoie permanenta de colectare.
Oricum cand exista legaturi intre obiecte din generatii diferite, se supun procesului GC si
generatiile vechi.

Controlul programabil al GC

Public Properties
MaxGeneration
Supported by the .NET Compact Framework.
Gets the maximum number of generations the
system currently supports.
Public Methods
Collect
Supported by the .NET Compact Framework.
Overloaded. Forces garbage collection.
GetGeneration
Overloaded. Returns the current generation number
of an object.
GetTotalMemory
Supported by the .NET Compact Framework.
Retrieves the number of bytes currently thought to
be allocated. A parameter indicates whether this
method can wait a short interval before returning
while the system collects garbage and finalizes
objects.
KeepAlive
Supported by the .NET Compact Framework.
References the specified object, making it ineligible
for garbage collection from the start of the current
routine to the point where this method is called.
ReRegisterForFinalize
Supported by the .NET Compact Framework.
Requests that the system call the finalizer method
for the specified object, for which SuppressFinalize
has previously been called.
SuppressFinalize
Supported by the .NET Compact Framework.
Requests that the system not call the finalizer
method for the specified object.
WaitForPendingFinalizers
Supported by the .NET Compact Framework.
Suspends the current thread until the thread
processing the queue of finalizers has emptied that
queue.

S-ar putea să vă placă și