Programovanie

Dokončenie a vyčistenie objektu

Pred tromi mesiacmi som začal minisériu článkov o navrhovaní objektov s diskusiou o princípoch návrhu, ktorá bola zameraná na správnu inicializáciu na začiatku života objektu. V tomto Dizajnové techniky tohto článku sa budem venovať princípom návrhu, ktoré vám pomôžu zabezpečiť správne vyčistenie na konci životnosti objektu.

Prečo upratovať?

Každý objekt v programe Java používa výpočtové zdroje, ktoré sú konečné. Je zrejmé, že všetky objekty používajú určitú pamäť na ukladanie svojich obrázkov na haldu. (Platí to aj pre objekty, ktoré nedeklarujú žiadne inštančné premenné. Každý obrázok objektu musí obsahovať určitý druh ukazovateľa na údaje o triede a môže obsahovať aj ďalšie informácie závislé od implementácie.) Ale objekty môžu okrem pamäte používať aj iné obmedzené zdroje. Niektoré objekty môžu napríklad používať zdroje, ako sú napríklad úchyty súborov, grafické kontexty, sokety atď. Pri navrhovaní objektu sa musíte ubezpečiť, že nakoniec uvoľní všetky obmedzené zdroje, ktoré používa, aby systému tieto prostriedky nedôjde.

Pretože Java je jazyk zhromažďovaný odpadkami, uvoľnenie pamäte spojenej s objektom je ľahké. Všetko, čo musíte urobiť, je pustiť všetky odkazy na objekt. Pretože sa nemusíte obávať explicitného uvoľnenia objektu, čo musíte urobiť v jazykoch ako C alebo C ++, nemusíte si robiť starosti s poškodením pamäte náhodným uvoľnením toho istého objektu dvakrát. Musíte sa však uistiť, že skutočne uvoľníte všetky odkazy na objekt. Ak to neurobíte, môžete skončiť s únikom pamäte, rovnako ako úniky pamäte, ktoré získate v programe C ++, keď zabudnete na výslovné uvoľnenie objektov. Pokiaľ však uvoľníte všetky odkazy na objekt, nemusíte sa obávať výslovného „uvoľnenia“ tejto pamäte.

Rovnako si nemusíte robiť starosti s tým, že explicitne uvoľníte všetky objekty, na ktoré sa odkazuje, premennými inštancie objektu, ktorý už nepotrebujete. Uvoľnením všetkých odkazov na nepotrebný objekt sa v skutočnosti zneplatnia všetky odkazy na základné objekty obsiahnuté v premenných inštancie tohto objektu. Ak by teraz zneplatnené odkazy boli jedinými zostávajúcimi odkazmi na tieto základné objekty, tieto základné objekty budú k dispozícii aj na zhromažďovanie odpadu. Hračka, nie?

Pravidlá zberu odpadu

Aj keď zhromažďovanie odpadu skutočne robí správu pamäte v Jave oveľa jednoduchšou ako v C alebo C ++, pri programovaní v Jave nie ste schopní úplne zabudnúť na pamäť. Ak chcete vedieť, kedy možno budete musieť premýšľať o správe pamäte v prostredí Java, musíte vedieť niečo o spôsobe spracovania odpadu v špecifikáciách Java.

Zber odpadu nie je povinný

Prvá vec, ktorú treba vedieť, je, že bez ohľadu na to, ako usilovne hľadáte špecifikáciu Java Virtual Machine (JVM Spec), nenájdete nijakú vetu, ktorá by velila, Každý JVM musí mať smetiara. Špecifikácia virtuálneho stroja Java poskytuje návrhárom virtuálnych počítačov veľkú voľnosť pri rozhodovaní o tom, ako ich implementácia bude spravovať pamäť, vrátane rozhodnutia, či vôbec alebo vôbec nebudú používať odvoz odpadu. Je teda možné, že niektoré JVM (napríklad JVM s čipovou kartou typu „bare-bone“) môžu vyžadovať, aby sa programy vykonávané v každej relácii „zmestili“ do dostupnej pamäte.

Samozrejme, kedykoľvek vám môže dôjsť pamäť, dokonca aj vo virtuálnom pamäťovom systéme. Špecifikácia JVM neuvádza, koľko pamäte bude k dispozícii pre JVM. Iba sa v ňom uvádza, že kedykoľvek je JVM robí došla pamäť, malo by to vyhodiť OutOfMemoryError.

Napriek tomu, aby mala väčšina aplikácií Java najlepšiu šancu na spustenie bez vyčerpania pamäte, väčšina serverov JVM použije zberač odpadu. Zberač odpadu regeneruje pamäť obsadenú nereferenčnými objektmi na halde, takže pamäť je možné znova použiť novými objektmi, a zvyčajne haldu pri spustení programu de-fragmentuje.

Algoritmus odvozu odpadu nie je definovaný

Ďalším príkazom, ktorý nenájdete v špecifikácii JVM, je Všetky JVM, ktoré používajú odvoz odpadu, musia používať algoritmus XXX. Dizajnéri každého JVM sa musia rozhodnúť, ako bude zber odpadkov fungovať pri ich implementácii. Algoritmus zberu odpadu je jednou z oblastí, v ktorej sa môžu predajcovia JVM usilovať o to, aby bola ich implementácia lepšia ako konkurencia. To je pre vás ako programátora Java dôležité z tohto dôvodu:

Pretože všeobecne neviete, ako sa bude v rámci JVM vykonávať odvoz odpadu, neviete, kedy sa bude zbierať odpadky z konkrétneho objektu.

No a čo? môžete sa opýtať. Dôvod, prečo by vás mohlo zaujímať, keď sa objekt zhromažďuje, súvisí s finalizátormi. (A. finalizátor je definovaná ako bežná metóda inštancie jazyka Java s názvom dokončiť () ktorá vráti neplatnosť a nebude mať žiadne argumenty.) Špecifikácie Java dávajú pri finalizátoroch nasledujúci sľub:

Pred opätovným získaním pamäte obsadenej objektom, ktorý má finalizátor, zberač odpadu vyvolá finalizátor tohto objektu.

Vzhľadom na to, že neviete, kedy sa budú objekty zbierať odpadky, ale viete, že finalizovateľné objekty sa finalizujú, pretože sa zhromažďujú odpadky, môžete urobiť nasledujúci veľký odpočet:

Neviete, kedy budú objekty finalizované.

Mali by ste vtlačiť túto dôležitú skutočnosť do svojho mozgu a navždy mu umožniť, aby informoval vaše návrhy objektov Java.

Finalizátory, ktorým sa treba vyhnúť

Hlavné pravidlo týkajúce sa finalizátorov je toto:

Nenavrhujte svoje programy Java tak, aby správnosť závisela od „včasnej“ finalizácie.

Inými slovami, nepíšte programy, ktoré sa rozbijú, ak sa niektoré objekty nedokončia o určité body v priebehu vykonávania programu. Ak napíšete takýto program, môže pracovať na niektorých implementáciách JVM, ale na iných zlyhať.

Nespoliehajte sa na to, že finalizátory uvoľnia iné ako pamäťové prostriedky

Príkladom objektu, ktorý porušuje toto pravidlo, je ten, ktorý otvorí súbor v jeho konštruktore a zatvorí ho v jeho konštruktore dokončiť () metóda. Aj keď sa tento dizajn javí ako čistý, upravený a symetrický, potenciálne vytvára zákernú chybu. Program Java bude mať spravidla k dispozícii iba konečný počet spracovaní súborov. Keď sa všetky tieto popisovače používajú, program nebude môcť otvoriť ďalšie súbory.

Program Java, ktorý využíva taký objekt (ten, ktorý otvorí súbor v konštruktore a zatvorí ho vo svojom finalizátore), môže fungovať dobre pri niektorých implementáciách JVM. Pri takýchto implementáciách by finalizácia prebiehala dosť často na to, aby bol neustále k dispozícii dostatočný počet popisovačov súborov. Ale ten istý program môže zlyhať na inom JVM, ktorého zberač odpadu sa nedokončí dosť často na to, aby programu nedochádzalo množstvo súborov. Alebo čo je ešte zákernejšie, program môže teraz pracovať na všetkých implementáciách JVM, ale zlyhá v kritickej situácii niekoľko rokov (a cykly uvoľňovania).

Ďalšie finalizačné pravidlá

Dve ďalšie rozhodnutia, ktoré zostávajú návrhárom JVM, sú výber vlákna (alebo vlákien), ktoré budú vykonávať finalizátory, a poradie, v akom budú finalizátory spustené. Finalizátory môžu byť spustené v ľubovoľnom poradí - postupne v jednom vlákne alebo súčasne vo viacerých vláknach. Ak váš program nejako závisí od správnosti od finalizátorov, ktoré sú spustené v konkrétnom poradí alebo podľa konkrétneho vlákna, môže to fungovať na niektorých implementáciách JVM, ale na iných zlyhať.

Mali by ste tiež pamätať na to, že Java považuje objekt za finalizovaný, či dokončiť () metóda sa vráti normálne alebo sa náhle dokončí vyvolaním výnimky. Zberatelia odpadu ignorujú všetky výnimky vyvolané finalizátormi a žiadnym spôsobom neinformujú zvyšok aplikácie, že došlo k výnimke. Ak potrebujete zaistiť, aby konkrétny finalizátor úplne splnil určité poslanie, musíte tento finalizátor napísať tak, aby zvládal všetky výnimky, ktoré sa môžu vyskytnúť predtým, ako finalizátor dokončí svoju misiu.

Jedno ďalšie základné pravidlo týkajúce sa finalizátorov sa týka objektov, ktoré zostali na halde na konci životnosti aplikácie. V rámci predvoleného nastavenia nebude garbage collector pri ukončení aplikácie vykonávať finalizátory akýchkoľvek objektov, ktoré zostali na halde. Ak chcete zmeniť toto predvolené nastavenie, musíte vyvolať runFinalizersOnExit () metóda triedy Beh programu alebo Systém, absolvovanie pravda ako jediný parameter. Ak váš program obsahuje objekty, ktorých finalizátory sa musia nevyhnutne vyvolať pred ukončením programu, nezabudnite ich vyvolať runFinalizersOnExit () niekde vo vašom programe.

Na čo sú teda finalizátory dobré?

Teraz už môžete mať pocit, že finalizátory veľmi nepoužívate. Aj keď je pravdepodobné, že väčšina navrhovaných tried nebude obsahovať finalizátor, existuje niekoľko dôvodov na použitie finalizátorov.

Jednou z rozumných, hoci zriedkavých aplikácií na finalizáciu, je uvoľnenie pamäte alokovanej natívnymi metódami. Ak objekt vyvolá natívnu metódu, ktorá prideľuje pamäť (možno funkciu C, ktorá volá malloc ()), finalizátor tohto objektu by mohol vyvolať natívnu metódu, ktorá uvoľní túto pamäť (hovory zadarmo()). V tejto situácii by ste použili finalizátor na uvoľnenie pamäte pridelenej v mene objektu - pamäte, ktorá nebude automaticky uvoľnená zberačom odpadu.

Ďalším, bežnejším použitím finalizátorov je poskytnutie záložného mechanizmu na uvoľnenie konečných zdrojov, ktoré nie sú v pamäti, ako sú napríklad úchyty súborov alebo zásuvky. Ako už bolo spomenuté, nemali by ste sa spoliehať na finalizátory pri uvoľňovaní konečných iných ako pamäťových prostriedkov. Namiesto toho by ste mali poskytnúť metódu, ktorá uvoľní zdroj. Možno však budete chcieť zahrnúť aj finalizátor, ktorý skontroluje, či bol zdroj už uvoľnený, a ak nie, pokračuje ďalej a uvoľní ho. Takýto finalizátor chráni pred (a dúfajme, že nepodporí) nedbalé používanie vašej triedy. Ak programátor klienta zabudne vyvolať metódu, ktorú ste uviedli na uvoľnenie prostriedku, finalizátor zdroj uvoľní, ak sa objekt niekedy zhromaždí v odpadkoch. The dokončiť () metóda LogFileManager trieda, uvedená ďalej v tomto článku, je príkladom tohto druhu finalizátora.

Vyhnite sa zneužitiu finalizátora

Existencia finalizácie spôsobuje niektoré zaujímavé komplikácie pre JVM a niektoré zaujímavé možnosti pre programátorov Java. Finalizácia poskytuje programátorom moc nad životnosťou a smrťou objektov. Stručne povedané, v Jave je možné a úplne legálne vzkriesiť objekty vo finalizátoroch - vrátiť ich späť do života tým, že sa na ne znova odkazuje. (Jedným zo spôsobov, ako by to mohol finalizátor dosiahnuť, je pridanie odkazu na finalizovaný objekt do statického prepojeného zoznamu, ktorý je stále „živý“.) Aj keď môže byť takáto sila lákavá na uplatnenie, pretože sa vďaka nej cítite dôležito, platí pravidlo je odolať pokušeniu použiť túto moc. Všeobecne vzkriesenie objektov vo finalizátoroch predstavuje zneužitie finalizátora.

Hlavné odôvodnenie tohto pravidla je, že každý program, ktorý používa vzkriesenie, môže byť prepracovaný do ľahšie pochopiteľného programu, ktorý nepoužíva vzkriesenie. Formálny dôkaz tejto vety je ponechaný ako cvičenie pre čitateľa (vždy som to chcel povedať), ale v neformálnom duchu zvážte, že vzkriesenie objektu bude rovnako náhodné a nepredvídateľné ako finalizácia objektu. Z tohto dôvodu bude design, ktorý využíva vzkriesenie, ťažké zistiť budúcemu programátorovi údržby, ktorý sa stane pri tom - kto nemusí úplne pochopiť zvláštnosti zberu odpadu v Jave.

Ak máte pocit, že musíte jednoducho vrátiť predmet späť do života, zvážte klonovanie novej kópie objektu namiesto toho, aby ste vzkriesili ten istý starý objekt. Dôvodom tejto rady je, že smetiari v JVM sa dovolávajú dokončiť () metóda objektu iba raz. Ak je tento objekt vzkriesený a stane sa dostupným na odvoz odpadu druhýkrát, bude objekt dokončiť () metóda nebude znovu vyvolaná.

$config[zx-auto] not found$config[zx-overlay] not found