Programavimas

Objekto užbaigimas ir valymas

Prieš tris mėnesius pradėjau mini straipsnių ciklą apie objektų projektavimą, aptarti projektavimo principus, kurie sutelkė dėmesį į tinkamą inicijavimą objekto gyvenimo pradžioje. Šiame Dizaino technika Straipsnyje aš sutelksiu dėmesį į projektavimo principus, kurie padės jums užtikrinti tinkamą valymą objekto gyvenimo pabaigoje.

Kodėl reikia valyti?

Kiekvienas „Java“ programos objektas naudoja baigtinius skaičiavimo išteklius. Akivaizdu, kad visi objektai naudoja šiek tiek atminties savo atvaizdams kaupti. (Tai pasakytina net apie objektus, kurie nedeklaruoja jokių egzempliorių kintamųjų. Kiekviename objekto vaizde turi būti tam tikras rodiklis į klasės duomenis ir jis gali apimti ir kitą nuo įgyvendinimo priklausančią informaciją.) Tačiau objektai gali naudoti ir kitus ribotus išteklius, išskyrus atmintį. Pavyzdžiui, kai kurie objektai gali naudoti tokius išteklius kaip failų rankenos, grafikos kontekstai, lizdai ir pan. Kai kuriate objektą, turite įsitikinti, kad jis galų gale išlaisvina visus naudojamus baigtinius išteklius, kad sistemoje tų išteklių netrūktų.

Kadangi „Java“ yra šiukšlių surinkta kalba, lengva atlaisvinti su objektu susietą atmintį. Viskas, ką jums reikia padaryti, tai atsisakyti visų nuorodų į objektą. Kadangi jums nereikia jaudintis dėl to, kad aiškiai atlaisvinsite objektą, kaip jūs turite tokiomis kalbomis kaip „C“ arba „C ++“, jums nereikia jaudintis dėl atminties sugadinimo netyčia atlaisvinant tą patį objektą du kartus. Tačiau jūs turite įsitikinti, kad iš tikrųjų atleidote visas nuorodas į objektą. Jei to nepadarysite, gali atsirasti atminties nutekėjimas, kaip ir atminties nutekėjimas, kurį gaunate C ++ programoje, kai pamiršite aiškiai atlaisvinti objektus. Nepaisant to, kol atleidžiate visas nuorodas į objektą, neturite jaudintis, kad aiškiai „atlaisvinsite“ tą atmintį.

Panašiai nereikia jaudintis dėl to, kad aiškiai atlaisvinsite visus objektus, nurodytus jums nebereikalingų objektų egzempliorių kintamaisiais. Atleidus visas nuorodas į nereikalingą objektą, bus anuliuotos visos sudedamosios objekto nuorodos, esančios to objekto egzemplioriaus kintamuosiuose. Jei dabar negaliojančios nuorodos buvo vienintelės likusios nuorodos į tuos sudedamuosius objektus, sudedamuosius objektus taip pat bus galima rinkti šiukšlėms. Pyrago gabalėlis, tiesa?

Šiukšlių surinkimo taisyklės

Nors šiukšlių surinkimas iš tiesų palengvina „Java“ atminties valdymą, palyginti su C arba C ++, programuodami „Java“ negalite visiškai pamiršti atminties. Norėdami žinoti, kada gali tekti galvoti apie „Java“ atminties valdymą, turite šiek tiek žinoti apie tai, kaip šiukšlių surinkimas traktuojamas „Java“ specifikacijose.

Šiukšlių surinkimas nėra įpareigotas

Pirmiausia reikia žinoti, kad ir kaip kruopščiai ieškotumėte „Java Virtual Machine Specification“ (JVM Spec), negalėsite rasti jokio sakinio, kuris komanduotų, Kiekvienas JVM privalo turėti šiukšlių surinkėją. „Java“ virtualiosios mašinos specifikacija suteikia VM dizaineriams daug laisvės spręsti, kaip jų diegimai valdys atmintį, įskaitant sprendimą, ar apskritai naudoti šiukšlių rinkimą. Taigi gali būti, kad kai kurie JVM (pvz., Plikų kaulų intelektualioji kortelė JVM) gali reikalauti, kad kiekvienos sesijos metu vykdomos programos „tilptų“ į turimą atmintį.

Žinoma, visada gali pritrūkti atminties, net ir virtualioje atminties sistemoje. „JVM Spec“ nenurodo, kiek atminties bus JVM. Joje tik teigiama, kad kai tik JVM daro baigsis atmintis, tai turėtų mesti „OutOfMemoryError“.

Nepaisant to, kad „Java“ programoms būtų suteikta geriausia galimybė vykdyti, neišeikvojant atminties, dauguma JVM naudos šiukšlių surinkėją. Šiukšlių surinkėjas susigrąžina atmintį, kurią užima nereikalaujami objektai ant krūvos, kad atmintį vėl galėtų naudoti nauji objektai, ir paprastai skaido krūvą, kai programa veikia.

Šiukšlių surinkimo algoritmas nėra apibrėžtas

Kita komanda, kurios nerasite JVM specifikacijoje, yra Visi JVM, kurie naudoja šiukšlių surinkimą, turi naudoti „XXX“ algoritmą. Kiekvieno JVM dizaineriai gali nuspręsti, kaip šiukšlių surinkimas veiks juos įgyvendinant. Šiukšlių surinkimo algoritmas yra viena iš sričių, kurioje JVM pardavėjai gali stengtis, kad jų įgyvendinimas būtų geresnis nei konkurentų. Tai svarbu jums kaip „Java“ programuotojui dėl šios priežasties:

Kadangi jūs paprastai nežinote, kaip bus vykdomas šiukšlių surinkimas JVM viduje, jūs nežinote, kada bet kuris konkretus objektas bus renkamas šiukšles.

Tai kas? galite paklausti. Priežastis, dėl kurios jums gali rūpėti, kai objektas surenkamas šiukšles, yra susijusi su užbaigėjais. (A užbaigėjas apibrėžiamas kaip įprastas „Java“ egzempliorių metodas, pavadintas baigti () kuris grąžinamas negaliojančiu ir nereiškia jokių argumentų.) „Java“ specifikacijos žada tokį pažadą apie užbaigėjus:

Prieš susigrąžindamas atmintį, kurią užima objektas, turintis užbaigimo programą, šiukšlių surinkėjas iškvies to objekto užbaigimo programą.

Atsižvelgiant į tai, kad nežinote, kada objektai bus renkami šiukšlėmis, tačiau žinote, kad baigtini objektai bus baigti, nes jie yra surenkami šiukšliai, galite atlikti tokį didelį išskaitymą:

Jūs nežinote, kada objektai bus baigti.

Turėtumėte įsirašyti šį svarbų faktą į savo smegenis ir visam laikui leisti jiems informuoti apie jūsų „Java“ objektų dizainą.

Finalistų vengti

Pagrindinė „finalistų“ taisyklė yra tokia:

Nekurkite savo „Java“ programų taip, kad teisingumas priklausytų nuo „savalaikio“ užbaigimo.

Kitaip tariant, nerašykite programų, kurios suges, jei tam tikri objektai nebus užbaigti tam tikrais programos vykdymo laikotarpio momentais. Jei rašote tokią programą, ji gali veikti įgyvendinant kai kuriuos JVM, bet nepavyksta dėl kitų.

Nepasitikėkite baigimo priemonėmis, kad išleistumėte ne atminties išteklius

Objektas, kuris pažeidžia šią taisyklę, yra tas, kuris atidaro failą jo konstruktoriuje ir uždaro failą baigti () metodas. Nors šis dizainas atrodo tvarkingas, tvarkingas ir simetriškas, jis gali sukurti klastingą klaidą. „Java“ programa paprastai turės tik ribotą skaičių failų tvarkyklių. Kai bus naudojamos visos šios rankenos, programa nebegalės atidaryti daugiau failų.

„Java“ programa, naudojanti tokį objektą (tą, kuris atidaro failą jo konstruktoriuje ir uždaro jį savo užbaigiklyje), gali gerai veikti kai kuriuose JVM diegimuose. Tokius diegimus užbaigiant būtų galima atlikti pakankamai dažnai, kad visada būtų pakankamai failų rankenų. Ta pati programa gali nepavykti kitam JVM, kurio šiukšlių surinkėjas neužbaigia pakankamai dažnai, kad programoje netrūktų failų tvarkyklių. Arba, kas dar klastingiau, programa dabar gali veikti visuose JVM diegimuose, tačiau kelerius metus (ir išleidimo ciklus) kelerius metus (ir išleidimo ciklus) gali nepavykti kritinės misijos atveju.

Kitos galutinės taisyklės

Du kiti sprendimai, palikti JVM dizaineriams, yra pasirinkti gijas (arba gijas), kuri atliks užbaigėjus, ir tvarką, kuria bus vykdomi galutiniai dalyviai. Galutiniai dalyviai gali būti vykdomi bet kokia tvarka - nuosekliai vienu gija arba vienu metu keliais gijomis. Jei jūsų programa teisingai priklauso nuo to, ar galutiniai programos dalyviai vykdomi tam tikra tvarka arba tam tikra gija, ji gali veikti su kai kuriais JVM diegimais, bet nepavyksta su kitais.

Taip pat turėtumėte nepamiršti, kad „Java“ mano, kad objektas turi būti baigtas, ar „ baigti () metodas grįžta normaliai arba užbaigiamas staiga išmetant išimtį. Šiukšlių surinkėjai nepaiso jokių išimčių, kurias pateikė galutiniai dalyviai, ir jokiu būdu nepraneša likusiai paraiškai, kad buvo išimtis. Jei turite įsitikinti, kad konkretus projekto vykdytojas visiškai įvykdo tam tikrą misiją, turite parašyti tą užbaigėją taip, kad jis atliktų visas išimtis, kurios gali atsirasti prieš užbaigėjui užbaigiant savo misiją.

Dar viena nykščio taisyklė apie užbaigėjus yra susijusi su objektais, likusiais ant krūvos programos gyvavimo pabaigoje. Pagal numatytuosius nustatymus šiukšlių surinkėjas nevykdys jokių objektų, likusių ant krūvos, baigiant programą, kai bus baigta programa. Norėdami pakeisti šį numatytąjį nustatymą, turite iškviesti runFinalizersOnExit () klasės metodas Veikimo laikas arba Sistema, praeina tiesa kaip vienintelis parametras. Jei jūsų programoje yra objektų, kurių galutiniai programos dalyviai turi būti visiškai iškviesti prieš išeinant iš programos, būtinai iškvieskite runFinalizersOnExit () kažkur jūsų programoje.

Taigi, kam naudingi finalistai?

Dabar gali kilti jausmas, kad jūs nelabai naudojatės finalistais. Nors tikėtina, kad daugumoje jūsų sukurtų klasių nebus finalisto, yra keletas priežasčių, kodėl reikia naudoti finalistus.

Viena iš pagrįstų, nors ir retų programų, skirtų užbaigtiems įrankiams, yra atlaisvinti atmintį, paskirstytą vietiniais metodais. Jei objektas naudoja vietinį metodą, kuris paskirsto atmintį (galbūt C funkciją, kuri iškviečia malloc ()), to objekto užbaigėjas gali pasinaudoti savitu metodu, kuris atlaisvina tą atmintį (skambučiai Laisvas()). Esant tokiai situacijai, naudosite užbaigimo priemonę, kad atlaisvintumėte atmintį, skirtą objekto vardu - atminties, kurios šiukšlių surinkėjas automatiškai neatgaus.

Kitas, labiau paplitęs, baigiamųjų įrankių naudojimas yra numatyti atsarginį mechanizmą, leidžiantį atminti ribotus išteklius, tokius kaip failų rankenos ar lizdai. Kaip minėta anksčiau, neturėtumėte pasikliauti galutiniais, išleisdami ribotus ne atminties išteklius. Vietoj to turėtumėte pateikti metodą, kuris išlaisvins šaltinį. Bet galbūt norėsite įtraukti užbaigimo priemonę, kuri patikrina, ar šaltinis jau išleistas, o jei dar nepaleido, tai tęsia ir išleidžia. Toks finalistas apsisaugo nuo atsainaus jūsų klasės naudojimo (ir, tikiuosi, neskatins). Jei kliento programuotojas pamiršta pasinaudoti metodu, kurį pateikėte, kad išleistumėte šaltinį, užbaigėjas išleis šaltinį, jei objekte kada nors bus surenkamos šiukšlės. baigti () metodas „LogFileManager“ klasė, parodyta vėliau šiame straipsnyje, yra tokio tipo užbaigimo pavyzdys.

Venkite piktnaudžiavimo užbaigimo programa

Galutinis užbaigimas kelia įdomių komplikacijų JVM ir įdomių galimybių „Java“ programuotojams. Tai, ką galutinai suteikia programuotojai, yra valdžia objektų gyvybei ir mirčiai. Trumpai tariant, „Java“ programoje įmanoma ir visiškai teisėta atgaivinti objektus baigiamosiose programose - atgaivinti juos, darant nuorodas į juos. (Vienas iš būdų, kurį atlikėjas gali tai padaryti, yra pridedant nuorodą į baigiamą objektą į statinį susietą sąrašą, kuris vis dar yra „gyvas“.) Nors tokia galia gali būti viliojanti mankšta, nes tai verčia jus jaustis svarbiu, nykščio taisykle yra atsispirti pagundai pasinaudoti šia galia. Apskritai objektų prikėlimas baigiamuosiuose įrenginiuose yra piktnaudžiavimas užbaigimo priemonėmis.

Pagrindinis šios taisyklės pagrindimas yra tas, kad bet kuri programa, naudojanti prisikėlimą, gali būti pertvarkyta į lengviau suprantamą programą, kurioje nenaudojamas prisikėlimas. Oficialus šios teoremos įrodymas paliekamas skaitytojui kaip pratimas (aš visada norėjau tai pasakyti), tačiau neoficialiai apsvarstykite, kad objekto prisikėlimas bus toks pat atsitiktinis ir nenuspėjamas kaip objekto užbaigimas. Tokį dizainą, kuriame naudojamas prisikėlimas, bus sunku išsiaiškinti kitam techninės priežiūros programuotojui, kuris vyksta kartu - kuris gali ne iki galo suprasti „Java“ šiukšlių surinkimo savitumus.

Jei manote, kad tiesiog turite atgaivinti daiktą, apsvarstykite galimybę klonuoti naują objekto kopiją, o ne prikelti tą patį seną daiktą. Šio patarimo priežastis yra ta, kad šiukšlių surinkėjai JVM remiasi baigti () objekto metodas tik vieną kartą. Jei tas objektas prikeltas ir antrą kartą bus galima rinkti šiukšles, objekto baigti () metodas nebebus naudojamas.