Ohjelmointi

Kohteen viimeistely ja puhdistus

Kolme kuukautta sitten aloitin minisarjan esineiden suunnittelusta keskustelemalla suunnitteluperiaatteista, joissa keskityttiin asianmukaiseen alustamiseen kohteen elinkaaren alussa. Tässä Suunnittelutekniikat Artikkelissa keskityn suunnitteluperiaatteisiin, jotka auttavat sinua varmistamaan kunnollisen siivouksen kohteen käyttöiän lopussa.

Miksi siivota?

Jokainen Java-ohjelman objekti käyttää rajallisia laskentaresursseja. Ilmeisimmin kaikki objektit käyttävät muistia tallentaakseen kuvansa kasaan. (Tämä pätee myös kohteisiin, joissa ei ilmoiteta esiintymämuuttujia. Jokaisen objektikuvan on sisällettävä jonkinlainen osoitin luokan tiedoille ja se voi sisältää myös muita toteutuksesta riippuvia tietoja.) Mutta objektit voivat käyttää myös muita rajallisia resursseja muistin lisäksi. Jotkut objektit voivat esimerkiksi käyttää resursseja, kuten tiedostokahvoja, grafiikkayhteyksiä, pistorasioita ja niin edelleen. Kun suunnittelet objektia, sinun on varmistettava, että se lopulta vapauttaa kaikki käyttämänsä rajalliset resurssit, jotta järjestelmä ei loppu kyseisistä resursseista.

Koska Java on roskien keräämä kieli, objektiin liittyvän muistin vapauttaminen on helppoa. Sinun tarvitsee vain päästää irti kaikista viittauksista esineeseen. Koska sinun ei tarvitse huolehtia nimenomaisesta objektin vapauttamisesta, kuten sinun on tehtävä esimerkiksi C- tai C ++ -kielillä, sinun ei tarvitse huolehtia muistin vioittumisesta vapauttamalla vahingossa sama objekti kahdesti. Sinun on kuitenkin varmistettava, että todella vapautat kaikki viittaukset esineeseen. Jos et, voit päätyä muistivuotoon, aivan kuten muistivuodot, jotka saat C ++ -ohjelmassa, kun unohdat vapauttaa esineitä nimenomaisesti. Siitä huolimatta, niin kauan kuin vapautat kaikki viittaukset esineeseen, sinun ei tarvitse huolehtia nimenomaisesti "vapauttamisesta".

Vastaavasti sinun ei tarvitse huolehtia siitä, että vapautat nimenomaisesti kaikki osatekijät, joihin viittaavat objektit, joita et enää tarvitse. Kaikkien tarpeettomaan objektiin viittausten vapauttaminen mitätöi kaikki kyseisen objektin ilmentymämuuttujiin sisältyvät osatekijäviitteet. Jos nyt mitätöity viittaukset olivat ainoat jäljellä olevat viittaukset näihin osatekijöihin, osatekijät ovat myös käytettävissä roskakoriin. Pala kakkua, eikö?

Jätteiden keräyksen säännöt

Vaikka roskien kerääminen todella tekee Java-muistin hallinnasta paljon helpompaa kuin C- tai C ++ -sovelluksessa, et voi unohtaa muistia kokonaan, kun ohjelmoit Java-ohjelmassa. Jos haluat tietää, milloin joudut ehkä miettimään Java-muistin hallintaa, sinun on tiedettävä vähän tavasta, jolla roskapostia käsitellään Java-määrityksissä.

Jätteiden keräystä ei ole pakollista

Ensinnäkin on tiedettävä, että riippumatta siitä, kuinka ahkerasti etsit Java Virtual Machine Specification (JVM Spec) -tekniikkaa, et löydä yhtään komentoa, Jokaisella JVM: llä on oltava roskien keräilijä. Java-virtuaalikoneiden määrittely antaa virtuaalikoneiden suunnittelijoille paljon liikkumavaraa päättää, kuinka heidän toteutuksensa hallitsevat muistia, mukaan lukien päätös siitä, käytetäänkö edes roskakoria lainkaan. Siten on mahdollista, että jotkut JVM: t (kuten paljain luut älykortti JVM) saattavat vaatia, että jokaisessa istunnossa suoritetut ohjelmat "mahtuvat" käytettävissä olevaan muistiin.

Tietenkin muisti voi loppua aina, jopa virtuaalimuistijärjestelmässä. JVM Spec ei ilmoita kuinka paljon muistia JVM: lle on käytettävissä. Siinä todetaan vain, että aina kun JVM tekee muisti loppuu, sen pitäisi heittää OutOfMemoryError.

Kuitenkin, jotta Java-sovelluksille saadaan parhaat mahdollisuudet suorittaa ilman muistin loppumista, useimmat JVM: t käyttävät roskien keräilijää. Jätteenkerääjä kerää kasaan viittaamattomien kohteiden käyttämän muistin, jotta uudet objektit voivat käyttää muistia uudelleen, ja yleensä hajottaa kasan ohjelman suorituksen aikana.

Jätteiden keräysalgoritmia ei ole määritelty

Toinen komento, jota et löydä JVM-määrityksistä, on Kaikkien roskakoria käyttävien JVM: ien on käytettävä XXX-algoritmia. Jokaisen JVM: n suunnittelijat päättävät, miten roskien keräys toimii toteutuksissaan. Jätteiden keräysalgoritmi on yksi alue, jolla JVM-toimittajat voivat pyrkiä tekemään niiden toteuttamisesta kilpailua parempaa. Tämä on tärkeää sinulle Java-ohjelmoijana seuraavasta syystä:

Koska et yleensä tiedä, miten roskien keräys suoritetaan JVM: n sisällä, et tiedä milloin tietty esine kerätään roskiin.

Mitä sitten? saatat kysyä. Syy, josta saatat välittää, kun esine kerätään roskiin, liittyy loppukäyttäjiin. (A viimeistelijä on määritelty tavalliseksi Java-ilmentymämenetelmäksi nimeltä viimeistellä () joka palauttaa mitätön eikä sisällä argumentteja.) Java-määritykset antavat seuraavan lupauksen viimeistelyohjelmista:

Ennen loppuohjelmalla varustetun objektin käyttämän muistin palauttamista roskakorin käyttäjä käynnistää kyseisen objektin viimeistelijän.

Koska et tiedä, milloin objektit kerätään roskiin, mutta tiedät, että viimeisteltävät objektit viimeistellään, kun ne ovat kerättyä roskaa, voit tehdä seuraavan suuren vähennyksen:

Et tiedä milloin objektit viimeistellään.

Sinun tulisi merkitä tämä tärkeä seikka aivoihisi ja antaa sen ikuisesti antaa tietoa Java-objektien suunnittelusta.

Viimeistelyaineet välttää

Viimeistelijöitä koskeva keskeinen nyrkkisääntö on seuraava:

Älä suunnittele Java-ohjelmia niin, että virheiden korjaus riippuu "oikea-aikaisesta" viimeistelystä.

Toisin sanoen, älä kirjoita ohjelmia, jotka hajoavat, jos tietyt objektit eivät ole viimeistelty tietyillä pisteillä ohjelman suorittamisen elämässä. Jos kirjoitat tällaisen ohjelman, se voi toimia joissakin JVM: n toteutuksissa, mutta toisten epäonnistua.

Älä luota viimeistelylaitteisiin muiden kuin muistiresurssien vapauttamiseksi

Esimerkki objektista, joka rikkoo tämän säännön, on sellainen, joka avaa tiedoston konstruktorissaan ja sulkee tiedoston sen viimeistellä () menetelmä. Vaikka tämä muotoilu näyttää siistiltä, ​​siistiltä ja symmetriseltä, se luo mahdollisesti salakavalan virheen. Java-ohjelmalla on yleensä vain rajallinen määrä tiedostokäsittelyjä. Kun kaikki nämä kahvat ovat käytössä, ohjelma ei voi avata enää tiedostoja.

Java-ohjelma, joka käyttää tällaista objektia (joka avaa tiedoston konstruktorissaan ja sulkee sen viimeistelijässä), voi toimia hyvin joissakin JVM-toteutuksissa. Tällaisissa toteutuksissa viimeistely tapahtuisi riittävän usein pitämään riittävä määrä tiedostokahvoja aina käytettävissä. Mutta sama ohjelma voi epäonnistua eri JVM: ssä, jonka roskien kerääjä ei viimeistele tarpeeksi usein pitääkseen ohjelmaa loppumasta tiedostokahvoista. Tai mikä on vielä salakavalempaa, ohjelma saattaa toimia kaikissa JVM-toteutuksissa nyt, mutta epäonnistuu muutaman vuoden (ja julkaisusyklien) kriittisessä tilanteessa tiellä.

Muut viimeistelijän nyrkkisäännöt

Kaksi muuta päätöstä, jotka JVM: n suunnittelijoille on jätetty, valitsevat ketjun (tai ketjut), jotka suorittavat viimeistelijät, ja järjestyksen, jossa viimeistelijät suoritetaan. Viimeistelijät voidaan ajaa missä tahansa järjestyksessä - peräkkäin yhdellä säikeellä tai samanaikaisesti useilla säikeillä. Jos ohjelmasi riippuu jostain syystä viimeistelijöiden oikeellisuudesta tietyssä järjestyksessä tai tietyssä säikeessä, se voi toimia joissakin JVM-toteutuksissa, mutta epäonnistua muissa.

Muista myös, että Java pitää objektia viimeisteltynä, onko viimeistellä () method palaa normaalisti tai päättyy äkillisesti heittämällä poikkeuksen. Jätteiden kerääjät jättävät huomiotta lopullisten käyttäjien tekemät poikkeukset eivätkä missään tapauksessa ilmoita muulle sovellukselle, että poikkeus on heitetty. Jos sinun on varmistettava, että tietty viimeistelijä suorittaa tietyn tehtävän täysin, sinun on kirjoitettava viimeistelijä siten, että se käsittelee kaikki mahdolliset poikkeukset, jotka saattavat ilmetä ennen viimeistelijän tehtävän suorittamista.

Yksi nyrkkisääntö viimeistelylaitteista koskee esineitä, jotka on jätetty kasaan sovelluksen elinkaaren lopussa. Oletusarvon mukaan roskien keräilijä ei suorita kasaan jääneiden esineiden viimeistelijöitä sovelluksen loputtua. Voit muuttaa tätä oletusasetusta käynnistämällä runFinalizersOnExit () luokan menetelmä Ajonaika tai Järjestelmä, ohimennen totta yhtenä parametrina. Jos ohjelmasi sisältää objekteja, joiden viimeistelijät on ehdottomasti käytettävä ennen ohjelman poistumista, muista kutsua runFinalizersOnExit () jossain ohjelmassa.

Joten mihin finalistit ovat hyviä?

Tähän mennessä saatat tuntea, että sinulla ei ole paljon hyötyä finalisteista. Vaikka on todennäköistä, että suurin osa suunnittelemistasi luokista ei sisällä viimeistelijää, on olemassa joitain syitä finalistien käyttämiseen.

Yksi kohtuullinen, vaikkakin harvinainen hakemus viimeistelylaitteelle on vapauttaa muistia, joka on varattu alkuperäisillä menetelmillä. Jos objekti vetoaa natiiviin menetelmään, joka varaa muistin (ehkä C-funktio, joka kutsuu malloc ()), kyseisen objektin viimeistelijä voi käyttää natiivia menetelmää, joka vapauttaa muistin (puhelut vapaa()). Tässä tilanteessa käytettäisit viimeistelylaitetta vapauttamaan muistia, joka on varattu objektin puolesta - muistia, jota roskankerääjä ei ota automaattisesti talteen.

Toinen, yleisempi viimeistelylaitteiden käyttö on tarjota varamekanismi lopullisten resurssien, kuten muistikorttien tai pistorasioiden, vapauttamiseksi muistista. Kuten aiemmin mainittiin, sinun ei pitäisi luottaa viimeistelijöihin lopullisten ei-muistiresurssien vapauttamisessa. Sen sijaan sinun on annettava menetelmä, joka vapauttaa resurssin. Mutta voit myös haluta sisällyttää viimeistelijän, joka tarkistaa, että resurssi on jo vapautettu, ja jos se ei ole, se menee eteenpäin ja vapauttaa sen. Tällainen viimeistelijä suojaa luokkansa huolimattomalta käytöltä (ja toivottavasti ei kannusta). Jos asiakasohjelmoija unohtaa vedota antamaasi menetelmään resurssin vapauttamiseksi, viimeistelijä vapauttaa resurssin, jos objektia kerätään roskiin. viimeistellä () menetelmä LogFileManager luokka, joka on esitetty myöhemmin tässä artikkelissa, on esimerkki tällaisesta viimeistelijästä.

Vältä viimeistelijän väärinkäyttöä

Viimeistelyn olemassaolo tuottaa JVM: ille mielenkiintoisia komplikaatioita ja Java-ohjelmoijille mielenkiintoisia mahdollisuuksia. Viimeistely antaa ohjelmoijille valtaa esineiden elämään ja kuolemaan. Lyhyesti sanottuna Javassa on mahdollista ja täysin laillista herättää objektit viimeistelijöissä - herättää ne takaisin elämään tekemällä niihin viittaukset uudelleen. (Yksi tapa, jolla viimeistelijä voi saavuttaa tämän, on lisäämällä viittaus viimeisteltävään objektiin staattiseen linkitettyyn luetteloon, joka on edelleen "elävä".) Vaikka tällainen voima saattaa olla houkuttelevaa käyttää, koska se saa sinut tuntemaan itsesi tärkeäksi, nyrkkisääntö on vastustaa kiusausta käyttää tätä voimaa. Yleensä esineiden uudelleen herättäminen viimeistelylaitteissa merkitsee viimeistelijän väärinkäyttöä.

Tämän säännön pääasiallinen perustelu on, että kaikki ylösnousemusta käyttävät ohjelmat voidaan suunnitella uudelleen helpommin ymmärrettäväksi ohjelmaksi, joka ei käytä ylösnousemusta. Muodollinen todiste tästä lauseesta jätetään harjoitukseksi lukijalle (olen aina halunnut sanoa niin), mutta ajattele epävirallisessa hengessä, että esineiden ylösnousemus on yhtä satunnaista ja arvaamatonta kuin esineiden viimeistely. Sellaisenaan ylösnousemusta käyttävää mallia on vaikea selvittää seuraavalle huolto-ohjelmoijalle, joka tapahtuu - joka ei välttämättä ymmärrä täysin Java-jätteiden keräyksen erityispiirteitä.

Jos sinusta tuntuu, että sinun täytyy vain herättää esine elämään, harkitse uuden kopion kloonaamista objektista sen sijaan, että herätettäisiin sama vanha esine uudelleen. Tämän neuvon taustalla on, että JVM: n roskien keräilijät käyttävät viimeistellä () objektin menetelmä vain kerran. Jos kyseinen esine herätetään ylös ja tulee saataville roskien keräykseen toisen kerran, objektin viimeistellä () menetelmää ei käytetä uudelleen.