Programmering

Objektfinalisering og opprydding

For tre måneder siden begynte jeg en miniserie med artikler om å designe objekter med en diskusjon om designprinsipper som fokuserte på riktig initialisering i begynnelsen av et objekts liv. I dette Designteknikker artikkel, vil jeg fokusere på designprinsippene som hjelper deg med å sikre riktig opprydding på slutten av et objekts liv.

Hvorfor rydde opp?

Hvert objekt i et Java-program bruker beregningsressurser som er endelige. Mest åpenbart bruker alle gjenstander noe minne til å lagre bildene sine på dyngen. (Dette gjelder selv for objekter som deklarerer ingen forekomstvariabler. Hvert objektbilde må inneholde en slags peker til klassedata, og kan også inneholde annen implementeringsavhengig informasjon.) Men objekter kan også bruke andre begrensede ressurser i tillegg til minne. For eksempel kan noen objekter bruke ressurser som filhåndtak, grafikkontekster, stikkontakter og så videre. Når du designer et objekt, må du sørge for at det til slutt frigjør endelige ressurser det bruker, slik at systemet ikke går tom for disse ressursene.

Fordi Java er et søppeloppsamlet språk, er det enkelt å frigjøre minnet som er knyttet til et objekt. Alt du trenger å gjøre er å gi slipp på alle referanser til objektet. Fordi du ikke trenger å bekymre deg for å eksplisitt frigjøre et objekt, slik du må på språk som C eller C ++, trenger du ikke bekymre deg for å ødelegge minne ved å frigjøre det samme objektet to ganger. Du må imidlertid sørge for at du faktisk frigjør alle referanser til objektet. Hvis du ikke gjør det, kan du ende opp med en minnelekkasje, akkurat som minnelekkasjen du får i et C ++ - program når du glemmer å eksplisitt frigjøre objekter. Likevel, så lenge du slipper alle referanser til et objekt, trenger du ikke bekymre deg for å eksplisitt "frigjøre" det minnet.

På samme måte trenger du ikke bekymre deg for å eksplisitt frigjøre noen bestanddeler som det refereres til for eksempelvariablene til et objekt du ikke lenger trenger. Hvis du slipper alle referanser til det unødvendige objektet, ugyldiggjøres eventuelle konstituerende objektreferanser i objektets instansvariabler. Hvis de nå ugyldige referansene var de eneste gjenværende referansene til disse bestanddelene, vil de bestanddelene også være tilgjengelige for søppeloppsamling. Stykke kake, ikke sant?

Reglene for søppelinnsamling

Selv om søppeloppsamling faktisk gjør minneadministrasjon i Java mye enklere enn det er i C eller C ++, kan du ikke helt glemme minne når du programmerer i Java. For å vite når du kanskje trenger å tenke på minnestyring i Java, må du vite litt om måten søppeloppsamling behandles i Java-spesifikasjonene.

Søppeloppsamling er ikke pålagt

Det første du må vite er at uansett hvor flittig du søker gjennom Java Virtual Machine Specification (JVM Spec), vil du ikke kunne finne noen setning som befaler, Hver JVM må ha en søppeloppsamler. Java Virtual Machine Specification gir VM-designere mye spillerom for å bestemme hvordan implementeringene deres vil administrere minne, inkludert å bestemme om de i det hele tatt vil bruke søppel. Dermed er det mulig at noen JVM-er (for eksempel JVM med bare bein-smartkort) kan kreve at programmer som utføres i hver økt, "passer" i tilgjengelig minne.

Selvfølgelig kan du alltid gå tom for minne, selv på et virtuelt minnesystem. JVM-spesifikasjonen oppgir ikke hvor mye minne som vil være tilgjengelig for en JVM. Det sier bare at når en JVM gjør går tom for minne, bør det kaste et OutOfMemoryError.

Likevel, for å gi Java-applikasjoner den beste sjansen for å kjøre uten å gå tom for minne, vil de fleste JVM-er bruke en søppeloppsamler. Søppeloppsamleren gjenvinner minnet som er okkupert av objekter som ikke er referert til på haugen, slik at minnet kan brukes igjen av nye gjenstander, og defragmenterer vanligvis haugen når programmet kjører.

Søppeloppsamlingsalgoritme er ikke definert

En annen kommando du ikke finner i JVM-spesifikasjonen er Alle JVM-er som bruker søppelinnsamling, må bruke XXX-algoritmen. Designerne til hver JVM får bestemme hvordan søppeloppsamling skal fungere i implementeringene. Søppeloppsamlingsalgoritme er et område der JVM-leverandører kan streve for å gjøre implementeringen bedre enn konkurrentene. Dette er viktig for deg som Java-programmerer av følgende grunn:

Fordi du generelt ikke vet hvordan søppeloppsamling skal utføres inne i en JVM, vet du ikke når noe bestemt objekt vil bli samlet inn.

Hva så? spør du kanskje. Grunnen til at du kanskje bryr deg når en gjenstand blir samlet inn søppel, har å gjøre med sluttbehandlere. (EN sluttbehandler er definert som en vanlig Java-forekomstmetode som heter fullføre () som returnerer ugyldig og tar ingen argumenter.) Java-spesifikasjonene gir følgende løfte om sluttbehandlere:

Før gjenvinning av minnet okkupert av et objekt som har en ferdigbehandler, vil søppeloppkaller påkalle gjenstandens sluttbehandler.

Gitt at du ikke vet når gjenstander blir samlet inn søppel, men du vet at gjenstander som kan sluttbehandles, vil bli ferdigbehandlet ettersom de er søppel, kan du gjøre følgende store trekk:

Du vet ikke når gjenstander skal fullføres.

Du bør trykke dette viktige faktum på hjernen din og for alltid la den informere Java-objektdesignene dine.

Finaliserer å unngå

Den sentrale tommelfingerregel angående sluttbehandlere er denne:

Ikke design Java-programmene dine slik at korrekthet avhenger av "rettidig" sluttføring.

Med andre ord, ikke skriv programmer som går i stykker hvis visse objekter ikke blir fullført av bestemte punkter i løpet av programmets utførelse. Hvis du skriver et slikt program, kan det fungere på noen implementeringer av JVM, men mislykkes på andre.

Ikke stol på at sluttbehandlere frigjør ressurser som ikke er minne

Et eksempel på et objekt som bryter denne regelen er et som åpner en fil i konstruktøren og lukker filen i den fullføre () metode. Selv om dette designet virker pent, ryddig og symmetrisk, kan det potensielt skape en lumsk feil. Et Java-program vil vanligvis bare ha et endelig antall filhåndtak. Når alle disse håndtakene er i bruk, vil ikke programmet kunne åpne flere filer.

Et Java-program som bruker et slikt objekt (et som åpner en fil i konstruktøren og lukker den i sluttbehandleren) kan fungere bra på noen JVM-implementeringer. Ved slike implementeringer vil sluttføring forekomme ofte nok til å holde et tilstrekkelig antall filhåndtak tilgjengelig til enhver tid. Men det samme programmet kan mislykkes på en annen JVM, hvis søppeloppsamler ikke fullfører ofte nok til at programmet ikke går tom for filhåndtak. Eller hva er enda mer lumsk, programmet kan fungere på alle JVM-implementeringer nå, men mislykkes i en oppdragskritisk situasjon noen år (og frigjør sykluser) nedover veien.

Andre tommelfingerregler

To andre beslutninger som er overlatt til JVM-designere, er å velge tråden (eller tråder) som skal utføre finaliseringsprogrammet og rekkefølgen som finalisatorene kjøres i. Finaliserere kan kjøres i hvilken som helst rekkefølge - sekvensielt av en enkelt tråd eller samtidig av flere tråder. Hvis programmet ditt på en eller annen måte er avhengig av korrektheten ved at sluttbehandlere kjøres i en bestemt rekkefølge, eller av en bestemt tråd, kan det fungere på noen JVM-implementeringer, men mislykkes på andre.

Du bør også huske på at Java anser et objekt som ferdigstilt om fullføre () metoden returnerer normalt eller fullføres brått ved å kaste et unntak. Søppeloppsamlere ignorerer unntak fra sluttbehandlere og gir på ingen måte beskjed til resten av applikasjonen om at et unntak ble kastet. Hvis du trenger å sikre at en bestemt finaliserer fullfører et visst oppdrag, må du skrive finalisatoren slik at den håndterer unntak som kan oppstå før finalisereren fullfører oppdraget.

En tommelfingerregel om sluttbehandlere gjelder gjenstander som er igjen på haugen på slutten av applikasjonens levetid. Som standard vil ikke søppeloppsamleren utføre sluttbehandleren av gjenstander som er igjen på haugen når applikasjonen avsluttes. For å endre denne standarden, må du påkalle runFinalizersOnExit () metode for klassen Kjøretid eller System, passering ekte som enkeltparameter. Hvis programmet ditt inneholder objekter som absolutt må startes før programmet avsluttes, må du påberope deg runFinalizersOnExit () et eller annet sted i programmet ditt.

Så hva er sluttbehandlere bra for?

Nå har du kanskje fått følelsen av at du ikke har mye bruk for sluttbehandlere. Selv om det er sannsynlig at de fleste klassene du designer ikke inkluderer en finalizer, er det noen grunner til å bruke finalizers.

En rimelig, men sjelden applikasjon for en finalizer er å frigjøre minne tildelt ved hjelp av innfødte metoder. Hvis et objekt påkaller en innfødt metode som tildeler minne (kanskje en C-funksjon som ringer malloc ()), kan objektets sluttbehandler påkalle en innfødt metode som frigjør minnet (samtaler gratis()). I denne situasjonen vil du bruke ferdigbehandleren til å frigjøre minne som er tildelt på vegne av et objekt - minne som ikke automatisk blir gjenvunnet av søppeloppsamleren.

En annen, mer vanlig, bruk av sluttbehandlere er å gi en reservemekanisme for å frigjøre endelige ressurser som ikke er minne, slik som filhåndtak eller stikkontakter. Som nevnt tidligere, bør du ikke stole på sluttbehandlere for å gi ut endelige ressurser som ikke er minne. I stedet bør du oppgi en metode som frigjør ressursen. Men det kan også være lurt å inkludere en finalizer som kontrollerer for å sikre at ressursen allerede er utgitt, og hvis den ikke har gjort det, fortsetter den og frigjør den. En slik finalist beskytter mot (og forhåpentligvis ikke vil oppmuntre) til slurvet bruk av klassen din. Hvis en klientprogrammerer glemmer å påkalle metoden du oppga for å frigjøre ressursen, vil finalisereren frigjøre ressursen hvis objektet noen gang blir samlet inn søppel. De fullføre () metoden for LogFileManager klasse, vist senere i denne artikkelen, er et eksempel på denne typen sluttbehandler.

Unngå misbruk av sluttbehandler

Eksistensen av finalisering gir noen interessante komplikasjoner for JVM og noen interessante muligheter for Java-programmerere. Hva finalisering gir til programmerere er makt over liv og død av objekter. Kort sagt, det er mulig og helt lovlig i Java å gjenopplive gjenstander i sluttbehandlere - å bringe dem tilbake til livet ved å gjøre dem referert igjen. (En måte en finalizer kan oppnå dette er ved å legge til en referanse til objektet som avsluttes i en statisk koblet liste som fremdeles er "live".) Selv om en slik kraft kan være fristende å utøve fordi den får deg til å føle deg viktig, er tommelfingerregelen er å motstå fristelsen til å bruke denne kraften. Generelt utgjør gjenoppliving av gjenstander i sluttbehandler misbruk av sluttbehandler.

Hovedbegrunnelsen for denne regelen er at ethvert program som bruker oppstandelse kan omdesignes til et lettere forståelig program som ikke bruker oppstandelse. Et formelt bevis på denne teoremet er igjen som en øvelse for leseren (jeg har alltid ønsket å si det), men i en uformell ånd, vurder at gjenoppstandelse vil være like tilfeldig og uforutsigbar som gjenstandsfinalisering. Som sådan vil et design som bruker oppstandelse være vanskelig å finne ut av den neste vedlikeholdsprogrammereren som skjer - som kanskje ikke helt forstår idiosynkrasiene i søppeloppsamling i Java.

Hvis du føler at du bare må bringe et objekt tilbake til livet, bør du vurdere å klone en ny kopi av objektet i stedet for å gjenopplive det samme gamle objektet. Begrunnelsen bak dette rådet er at søppeloppsamlere i JVM påberoper seg fullføre () metoden til et objekt bare en gang. Hvis objektet gjenoppstår og blir tilgjengelig for søppeloppsamling en gang til, er objektet det fullføre () metoden vil ikke bli påkalt igjen.

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