200 likes | 308 Views
Microsoft.NET környezet. Hernyák Zoltán Programozási Nyelvek II. Eszterházy Károly Főiskola Számítástudományi tsz. 1. Destructor. A konstruktor-ok a példány használata előtt hívódnak meg, a példány életciklusának elején. A destruktorok olyan speciális metódusok, amelyek
E N D
Microsoft.NET környezet Hernyák Zoltán Programozási Nyelvek II. Eszterházy Károly Főiskola Számítástudományi tsz 1
Destructor • A konstruktor-ok a példány használata előtt hívódnak • meg, a példány életciklusának elején. • A destruktorok olyan speciális metódusok, amelyek • az életciklus végén hívódnak meg. • A destruktorokban a példány megszűnése előtti • ‘nagytakarítás’-t kell elvégezni: • pluszban foglalt memória felszabadítása • megnyitott file-ok lezárása • nyomtatás befejezésének jelzése • hálózati kapcsolatok lezárása • stb… 2
Destructor A destruktorok neve kötelezően megegyezik az osztály nevével, de előtte egy ~ jel kell szerepeljen! class TFtpKliens { ~TFtpKliens() { // hálózati kapcsolatok lezárása ... } } A destruktornak nem lehet elérési szint módosítója (automatikusan public)! A destruktornak nem lehet paramétere sem! Ezért minden osztályhoz maximum 1 db destruktort készíthető! 3
Explicit destructor hívás Miért ez a sok megkötés a destruktorra? (#1) Történelmileg a destruktort először a programozók hívták meg explicit módon, valami hasonló módon: TVerem v = new TVerem(); … // v példány használata free v ~TVerem(); ( ez egyébként nem működik C#-ban !!!! A destruktort hívásakor szabadítódott fel a példányhoz tartozó memóriaterület is. Ha a programozó ‘elfelejtette’ ezt meghívni, akkor a példány ottragadt a memóriában, és memóriaszivárgás (memory leak) keletkezett. Az ilyen program ha sokáig futott a memóriában, akkor elég sok idő után a teljes memóriát ilyen ‘beragadt’, haszontalan példányok foglalták el. 4
Explicit destructor hívás • Tipikus hibák: • A programozó ‘elfelejti’ meghívni a felszabadítást. • Ekkor a példány ottragadt a memóriában, és memóriaszivárgás • (memory leak) keletkezett. Az ilyen program ha sokáig futott a • memóriában, akkor elég sok idő után a teljes memóriát ilyen • ‘beragadt’, haszontalan példányok foglalták el. • A programozó rosszkor (idő előtt) hívta meg a felszabadítást, a példányra még van hivatkozás. a = peldany; b = a; // b is ugyanarra a példányra mutat free a; // a példány eltűnik a memóriából b.muvelet(); // ez itt hiba forrása már Ekkor a program ‘bármit’ is csinálhat… de jót ritkán
Referenciaszámlálás elve • (#2) Erre megoldást nyújtott a referencia típusú változó: • Ennek során a futtató rendszer nyilvántartotta • nemcsak azt, hogy hol vannak a példányok számára • lefoglalt memóriaterületek, hanem azt is, hogy • hány változó hivatkozik az adott példányra. • ( referenciaszámlálás elve ) • Amikor egy példány memóriaterületét egy változóba • értékül adjuk, akkor a referenciaszámláló növelődik • 1-el. • Amikor egy ilyen változó másik értéket kap, vagy • megszűnik, akkor a referenciaszámláló csökken 1-el. • Amikor egy példányhoz tartozó referenciaszámláló • eléri a 0-t, akkor automatikusan fel lehet szabadítani • a hozzá tartozó memóriaterületet. 6
Referenciaszámlálás elve public int Akarmi() { TVerem v = new TVerem(); // v használata ... } Példány memória allokálása Referenciaszámláló = 1 A lokális ‘v’ változó megszűnik létezni Referenciaszámláló = 0 Példány törlése a memóriából automatikusan! A példány törlése során 1: meg kell hívni a destruktorát, hogy ‘éretsítődjön’ a példány, hogy törlése fog kerülni, amit még akar az ‘utolsó szó jogán’ azt tegye meg 2: a lefoglalt memória felszabadítása 7
Referenciaszámlálás elve Az ilyen referenciaszámlálás-elvű programozási nyelveken a destruktort már nem explicit módon hívja meg a programozó, hanem a futtató rendszer hívja meg automatikusan (implicit). A futtató rendszer viszont nem fog neki paramétert átadni (nem is tudna honnan), ezért itt már nincs értelme a destruktort paraméteresen megírni (nem is szabad). 8
Referenciaszámlálás elve A referenciaszámlálás nem okoz komolyabb mérvű lassulást a program futása során! Ugyanakkor a programozók azonnal megszerették, mert megszűnt a memóriaszivárgás, egyszerűsödött a program írása, egy hibalehetőség megszűnt (egy gonddal kevesebb). Ugyanakkor jegyezzük meg, hogy a referenciaszámlálás bonyolultabb esetben sajnos egyszerűen semmit sem ér ! 9
Referenciaszámlálás halála Tegyük fel, hogy egy A példány hivatkozik egy B példányra, és a B is hivatkozik az A példányra ( kölcsönös hivatkozás ). Ilyenek pl. a ciklikusan láncolt lista elemei! Referenciaszámláló = 2 változó = ”A” példány; ”A” példány ”B” példány Referenciaszámláló = 1 10
Referenciaszámlálás halála Ha a változó megszűnik, vagy már nem erre a példányra hivatkozik, akkor az ”A” példány a programból már elérhetetlenné vált. Ugyanakkor a referenciaszámlálója még mindig 1! Referenciaszámláló = 1 változó = null; ”A” példány ”B” példány Referenciaszámláló = 1 Ekkor az ”A” és a ”B” példány is beragadt a memóriában! Pedig ezt akartuk kikerülni… 11
Garbage Collector felemelkedése (#3) Egy komolyabb programban a példányok egymásra is hivatkoznak, és ez egy komoly hivatkozási hálót ( gráfot ) hoz létre. Egy egyszerű referencia számlálás kevés a felesleges példányok felfedezésére és kiszűrésére. a program ‘élő’ változói hivatkoznak a példányokra 12
Garbage Collector A megoldás, hogy a gráfot be kell járni (gráfbejáró algoritmussal, pl szélességi vagy mélységi bejárás) a program változóiból kiindulva. Amely példányhoz nem lehet eljutni az élek ( hivatkozások ) mentén, azok a példányok feleslegesek, és ki lehet őket törölni. Ezt a megvalósítást már nem referencia-számlálásnak nevezzük, hanem ‘szemétgyüjtésnek’. Szemét = garbage Gyűjtő = collector 13
Garbage Collector Amikor új példányt hozunk létre, akkor egy halom (heap) területen lefoglalunk egy egybefüggő szakaszt. Ezen területen az első szabad helyet egy változó jelöli, őt kell növelni a lefoglalandó terület méretével: Obj1 NextObjPtr
Obj4 Obj3 Garbage Collector Aztán jön a GC, elemzi a hivatkozási gráfot, és kiszúrja a felesleges példányokat. A gyökerek a program aktuálisan élő változói: NextObjPtr 1. Gyökerek 2. Elérhetőségi gráf Gyökerek 3. Takarítás Obj2 4. Tömörítés Obj1 5. Mutatók frissítése
Garbage Collector Ha a futtató rendszer GC-t használ, akkor a destruktort hívása automatikusan történik meg, amikor eljut odáig a GC. Ez általában nem azonnal történik meg, mint ahogy a példány ‘szemétté’ válik, időre van szükség. Ezért a destruktort hívásának időpontja nemdeterminisztikus. Az sem determinisztikus, hogy az ”A” vagy a ”B” példányra hívódik-e meg először a destruktor, és szabadítódik fel a memória. ”A” példány ”B” példány 16
Garbage Collector C#-ban a destruktort a programunkkal párhuzamosan, egy külön szálon fut. Másodpercenként több száz millió példányt képes felfedezni, és felszabadítani ha kell. Valamelyest lassítja a program futását, de ez elhanyagolható azon előny mellett, hogy garantáltan nincs memóriaszivárgás a programban, és a programozó nem véthet ilyen jellegű hibát. Pl: egy GC-vel ellátott nyelven a láncolt lista minden elemének törlése: A többit a GC végzi FejElem = null; 17
Garbage Collector Nagyon ritkán írunk destruktort, mert: - a pluszban lefoglalt memória is példány, és a GC majd azt is fel fogja szabadítani - a hálózati kapcsolat létrehozásához is példányosítani kell egy ‘hálózati kapcsolat’ osztályt, és ezen példányt a GC meg fogja találni, és meghívja rá az ő destruktorát, és akkor a hálózati kapcsolat automatikusan lezárja magát stb… Ezért csak nagyon speciális esetben kell nekünk magunknak explicit módon felszabadítani erőforrást. 18
Garbage Collector Valójában a C#-ban nincs is destruktor, csak destruktornak kinéző metódus. A destruktor szót egyéb OOP nyelvekből vette át a C#. A destruktort valójában a Finalize() metódus override-ja. class TSajat { ~TSajat() { ... } } class TSajat { protected override Finalize() { ... } } 19
Garbage Collector A C# fordítóprogramja a destruktorunkat automatikusan Finalize()-nek olvassa. De nem engedi meg, hogy közvetlenül a Finalize-t definiáljuk felül. Ha az ős osztálynak is van destruktora, akkor az le fog futni, mielőtt a mi destruktorunk elindulna hasonlóan a konstruktorok hívási láncához. 20