Programmēšana

Objekta pabeigšana un tīrīšana

Pirms trim mēnešiem es sāku rakstu mini sēriju par objektu projektēšanu, apspriežot dizaina principus, kas koncentrējās uz pareizu inicializēšanu objekta dzīves sākumā. Šajā Dizaina paņēmieni rakstā es pievērsīšos dizaina principiem, kas palīdzēs jums nodrošināt pareizu tīrīšanu objekta dzīves beigās.

Kāpēc sakopt?

Katrs Java programmas objekts izmanto ierobežotus skaitļošanas resursus. Acīmredzot visi objekti izmanto kādu atmiņu, lai savus attēlus glabātu kaudzē. (Tas attiecas pat uz objektiem, kas nedeklarē nevienu mainīgo lielumu. Katrā objekta attēlā ir jāietver sava veida rādītājs klases datiem, un tajā var būt arī cita no ieviešanas atkarīga informācija.) Bet objekti var izmantot arī citus ierobežotus resursus, izņemot atmiņu. Piemēram, daži objekti var izmantot tādus resursus kā failu rokturi, grafikas konteksti, kontaktligzdas utt. Veidojot objektu, jums jāpārliecinās, vai tas galu galā atbrīvo visus izmantotos ierobežotos resursus, lai sistēmā šie resursi nebeigtos.

Tā kā Java ir atkritumu savākta valoda, ar objektu saistītās atmiņas atbrīvošana ir vienkārša. Viss, kas jums jādara, ir atlaist visas atsauces uz objektu. Tā kā jums nav jāuztraucas par objekta skaidru atbrīvošanu, kā tas ir jādara tādās valodās kā C vai C ++, jums nav jāuztraucas par atmiņas bojāšanu, nejauši atbrīvojot vienu un to pašu objektu divreiz. Tomēr jums ir jāpārliecinās, vai jūs faktiski atbrīvojat visas atsauces uz objektu. Ja tas nenotiek, jūs varat beigties ar atmiņas noplūdi, tāpat kā atmiņas noplūde, kas rodas C ++ programmā, kad aizmirstat skaidri atbrīvot objektus. Tomēr, kamēr jūs atbrīvojat visas atsauces uz objektu, jums nav jāuztraucas par šīs atmiņas skaidru "atbrīvošanu".

Tāpat jums nav jāuztraucas par to, ka skaidri atbrīvojat visus komponentus, uz kuriem atsaucas vairs nevajadzīga objekta mainīgie. Atbrīvojot visas atsauces uz nevajadzīgo objektu, faktiski tiks anulētas visas objekta atsauces, kas atrodas šī objekta instances mainīgajos. Ja atsauces uz šiem sastāvdaļu objektiem bija tikai anulētās atsauces, tad atkritumu savākšanai būs pieejami arī komponenti. Kūkas gabals, vai ne?

Atkritumu savākšanas noteikumi

Kaut arī atkritumu savākšana patiešām atvieglo atmiņas pārvaldību Java, nekā tas ir C vai C ++, programmējot Java, jūs nevarat pilnībā aizmirst par atmiņu. Lai uzzinātu, kad jums var būt jādomā par atmiņas pārvaldību Java, jums mazliet jāzina par to, kā Java specifikācijās tiek apstrādāta atkritumu savākšana.

Atkritumu savākšana nav obligāta

Vispirms ir jāzina, ka neatkarīgi no tā, cik cītīgi meklējat Java virtuālās mašīnas specifikācijā (JVM Spec), jūs nevarēsit atrast nevienu komandu, Katrā JVM jābūt atkritumu savācējam. Java virtuālās mašīnas specifikācija VM dizaineriem dod lielu rīcības brīvību, lemjot par to, kā viņu ieviešana pārvaldīs atmiņu, tostarp izlemjot, vai vispār izmantot atkritumu savākšanu. Tādējādi ir iespējams, ka daži JVM (piemēram, kailā viedkarte JVM) var pieprasīt, lai katrā sesijā izpildītās programmas "ietilptu" pieejamajā atmiņā.

Protams, vienmēr var pietrūkt atmiņas pat virtuālajā atmiņas sistēmā. JVM specifikācijā nav norādīts, cik daudz atmiņas būs pieejamas JVM. Tas tikai norāda, ka vienmēr, kad JVM dara atmiņa beigsies, tai vajadzētu iemest OutOfMemoryError.

Neskatoties uz to, lai Java lietojumprogrammām būtu vislabākās iespējas izpildīt, nepietrūkot atmiņas, lielākā daļa JVM izmantos atkritumu savācēju. Atkritumu savācējs atgūst atmiņu, kuru aizņem kaudzē nenorādīti objekti, lai atmiņu atkal varētu izmantot jauni objekti, un parasti programmas darbības laikā sadrupina kaudzi.

Atkritumu savākšanas algoritms nav definēts

Vēl viena komanda, kuru jūs neatradīsit JVM specifikācijā, ir Visiem JVM, kas izmanto atkritumu savākšanu, jāizmanto algoritms XXX. Katra JVM dizaineriem ir jāizlemj, kā atkritumu savākšana darbosies to ieviešanā. Atkritumu savākšanas algoritms ir viena no jomām, kurā JVM pārdevēji var censties panākt, lai to ieviešana būtu labāka nekā konkurentu. Tas ir svarīgi jums kā Java programmētājam šāda iemesla dēļ:

Tā kā jūs parasti nezināt, kā JVM tiks veikta atkritumu savākšana, jūs nezināt, kad kāds konkrēts objekts tiks savākts.

Nu un kas? jūs varētu jautāt. Iemesls, kāpēc jūs varētu interesēt, kad objekts ir savākts atkritums, ir saistīts ar finišētājiem. (A finālists ir definēta kā parasta Java instances metode ar nosaukumu pabeigt () kas atgriež nederīgu un nepieņem argumentus.) Java specifikācijas sola par finālistiem šādu solījumu:

Pirms atgūt atmiņu, kuru aizņem objekts, kuram ir pabeigšanas funkcija, atkritumu savācējs izsauks šī objekta pabeigšanas ierīci.

Ņemot vērā to, ka jūs nezināt, kad objekti tiks savākti, bet jūs zināt, ka pabeigtie objekti tiks pabeigti, jo tie ir savākti atkritumi, varat veikt šādu lielu atskaitījumu:

Jūs nezināt, kad objekti tiks pabeigti.

Jums vajadzētu iespiest šo svarīgo faktu uz jūsu smadzenēm un uz visiem laikiem ļaut tiem informēt jūsu Java objektu dizainus.

Finaizētāji, no kuriem jāizvairās

Galvenais īkšķis, kas attiecas uz finālistiem, ir šāds:

Neveidojiet Java programmas tā, lai pareizība būtu atkarīga no "savlaicīgas" pabeigšanas.

Citiem vārdiem sakot, nerakstiet programmas, kas saplīsīs, ja noteikti objekti netiks pabeigti ar noteiktiem programmas izpildes dzīves punktiem. Ja jūs rakstāt šādu programmu, tā var darboties pie dažām JVM ieviešanām, bet citām neizdoties.

Lai nepiedāvātu resursus, kas nav atmiņā, nepaļaujieties uz finišētājiem

Objekta, kas pārkāpj šo kārtulu, piemērs ir tāds, kas atver failu tā konstruktorā un aizver failu tajā pabeigt () metodi. Lai gan šis dizains šķiet veikls, kārtīgs un simetrisks, tas potenciāli rada mānīgu kļūdu. Java programmas rīcībā parasti būs tikai ierobežots failu rokturu skaits. Kad visi šie rokturi tiek izmantoti, programma vairs nevarēs atvērt citus failus.

Java programma, kas izmanto šādu objektu (tādu, kas atver failu tā konstruktorā un aizver to galīgajā versijā), var labi darboties dažās JVM implementācijās. Šādās realizācijās pabeigšana notiktu pietiekami bieži, lai vienmēr būtu pieejams pietiekams skaits failu rokturu. Bet viena un tā pati programma var neizdoties citam JVM, kura atkritumu savācējs nepabeidz darbu pietiekami bieži, lai programmai nedarbotos failu rokturi. Vai, kas ir vēl mānīgāk, programma tagad var darboties visos JVM ieviešanas gadījumos, bet dažus gadus (un izlaišanas ciklus) pa ceļam neizdoties misijai kritiskā situācijā.

Citi finālista noteikumi

Divi citi JVM dizaineriem atstātie lēmumi ir pavedienu (vai pavedienu) atlase, kas veiks izpildītājus, un secība, kādā tiks izpildīti finišētāji. Galīgos variantus var palaist jebkurā secībā - secīgi ar vienu pavedienu vai vienlaikus ar vairākiem pavedieniem. Ja jūsu programmas pareizība kaut kādā veidā ir atkarīga no tā, vai finālisti tiek palaisti noteiktā secībā vai ar konkrētu pavedienu, tā var darboties dažos JVM ievieumos, bet citās neizdoties.

Jums arī jāpatur prātā, ka Java uzskata objektu par pabeigtu, vai pabeigt () metode atgriežas normāli vai tiek pabeigta pēkšņi, izmetot izņēmumu. Atkritumu savācēji ignorē jebkādus izņēmumus, ko radījuši finālisti, un nekādā gadījumā nepaziņo pārējai lietojumprogrammai, ka tika izmests izņēmums. Ja jums ir jāpārliecinās, ka konkrētais finišētājs pilnībā izpilda noteiktu uzdevumu, jums tas ir jāuzraksta tā, lai tas apstrādātu visus izņēmumus, kas var rasties pirms finālista uzdevuma izpildes.

Vēl viens īkšķis par finālistiem attiecas uz objektiem, kas palikuši kaudzē lietojumprogrammas darbības laikā. Pēc noklusējuma atkritumu savācējs neveido neviena objekta, kas palicis uz kaudzes, pabeigšanas programmas iziešanas laikā. Lai mainītu šo noklusējumu, jums jāizsauc runFinalizersOnExit () klases metode Izpildlaiks vai Sistēma, garāmejot taisnība kā vienu parametru. Ja jūsu programmā ir objekti, kuru pabeigšanas programmas absolūti jāizsauc pirms programmas iziešanas, noteikti izsauciet to runFinalizersOnExit () kaut kur jūsu programmā.

Tātad, kam ir labi finālisti?

Tagad jums var rasties sajūta, ka jums nav daudz ko izmantot finālistiem. Lai gan visticamāk, ka lielākajā daļā jūsu izstrādāto klašu nebūs finālista, ir daži iemesli, kāpēc izmantot finālistus.

Viena no pamatotajām, lai arī reti sastopamajām lietojumprogrammām galīgajam izstrādātājam ir atmiņas atbrīvošana, izmantojot vietējās metodes. Ja objekts izsauc vietējo metodi, kas piešķir atmiņu (varbūt C funkciju, kas izsauc malloc ()), šī objekta pabeigšanas programma varētu izmantot vietējo metodi, kas atbrīvo šo atmiņu (zvani bez maksas ()). Šajā situācijā jūs izmantotu pabeigšanas programmu, lai atbrīvotu atmiņu, kas piešķirta objekta vārdā - atmiņu, kuru atkritumu savācējs automātiski neatgūs.

Cits, biežāk lietots, finišētāju veids ir nodrošināt rezerves mehānismu, lai atbrīvotu ierobežotus resursus, kas nav atmiņa, piemēram, failu rokturi vai ligzdas. Kā minēts iepriekš, ierobežotu resursu, kas nav atmiņa, izlaišanai nevajadzētu paļauties uz pabeigšanas ierīcēm. Tā vietā jums jānorāda metode, kas atbrīvos resursu. Iespējams, vēlēsities iekļaut arī pabeigšanas programmu, kas pārbauda, ​​vai resurss jau ir izlaists, un, ja tas vēl nav izdarīts, tas iet uz priekšu un atbrīvo to. Šāds finālists pasargā no jūsu klases paviršas izmantošanas (un, cerams, neveicinās). Ja klienta programmētājs aizmirst izmantot metodi, kuru norādījāt, lai atbrīvotu resursu, pabeigtājs atbrīvos resursu, ja objekts kādreiz tiks savākts. The pabeigt () metode LogFileManager klase, kas parādīta vēlāk šajā rakstā, ir šāda veida finālistu piemērs.

Izvairieties no finalizer ļaunprātīgas izmantošanas

Pabeigšanas esamība rada dažas interesantas komplikācijas JVM un dažas interesantas iespējas Java programmētājiem. Tas, ko programmētāji piešķir pabeigšanai, ir vara pār objektu dzīvi un nāvi. Īsāk sakot, Java valodā ir iespējams un pilnīgi likumīgi atdzīvināt objektus pabeigšanas ierīcēs - atdzīvināt tos, liekot tiem atkal atsaukties. (Viens no veidiem, kā to varētu paveikt finālists, ir statiska sasaistītā saraksta pievienošana, kas joprojām ir “dzīvs”, pievienojot atsauci uz pabeigto objektu.) Lai gan šāda vara var būt vilinoša, jo tā liek justies svarīgai, īkšķis ir pretoties kārdinājumam izmantot šo spēku. Kopumā objektu atdzīvināšana finišētājos ir finālistu ļaunprātīga izmantošana.

Galvenais šī noteikuma pamatojums ir tāds, ka jebkuru programmu, kas izmanto augšāmcelšanos, var pārveidot par vieglāk saprotamu programmu, kas neizmanto augšāmcelšanos. Šīs teorēmas oficiāls pierādījums tiek atstāts kā vingrinājums lasītājam (es vienmēr to esmu gribējis pateikt), taču neoficiālā garā uzskatiet, ka objekta augšāmcelšanās būs tikpat nejauša un neparedzama kā objekta pabeigšana. Dizainu, kas izmanto augšāmcelšanos, būs grūti saprast nākamajam tehniskās apkopes programmētājam, kurš notiek līdzās - kurš, iespējams, līdz galam neizprot Java atkritumu savākšanas īpatnības.

Ja jums šķiet, ka jums vienkārši ir jāatgādina objekts, apsveriet jauna objekta eksemplāra klonēšanu, nevis tā paša vecā objekta atdzīvināšanu. Šī padoma pamatojums ir tāds, ka atkritumu savācēji JVM atsaucas uz pabeigt () objekta metode tikai vienu reizi. Ja šis objekts tiek augšāmcelts un otrreiz kļūst pieejams atkritumu savākšanai, objekta pabeigt () metodi vairs neizmantos.

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