En guide til å fullføre metoden i Java

1. Oversikt

I denne opplæringen vil vi fokusere på et kjerneaspekt av Java-språket - fullføre metoden gitt av roten Gjenstand klasse.

Enkelt sagt, dette kalles før søppeloppsamlingen for et bestemt objekt.

2. Bruke Finalizers

De fullfør () metoden kalles finalizer.

Finalister blir påkalt når JVM finner ut at denne spesielle forekomsten skal samles til søppel. En slik sluttbehandler kan utføre alle operasjoner, inkludert å bringe gjenstanden til liv igjen.

Hovedformålet med en sluttbehandler er imidlertid å frigjøre ressurser som brukes av objekter før de fjernes fra minnet. En sluttbehandler kan fungere som den primære mekanismen for opprydding, eller som et sikkerhetsnett når andre metoder mislykkes.

For å forstå hvordan en finalizer fungerer, la oss ta en titt på en klassedeklarasjon:

offentlig klasse Finaliserbar {privat BufferedReader-leser; public Finalizable () {InputStream input = this.getClass () .getClassLoader () .getResourceAsStream ("file.txt"); this.reader = new BufferedReader (new InputStreamReader (input)); } offentlig String readFirstLine () kaster IOException {String firstLine = reader.readLine (); returner firstLine; } // andre klassemedlemmer}

Klassen Endelig har et felt leser, som refererer til en ressurs som kan stenges. Når et objekt blir opprettet fra denne klassen, konstruerer det et nytt BufferedReader for eksempel å lese fra en fil i klassestien.

En slik forekomst brukes i readFirstLine metode for å trekke ut den første linjen i den gitte filen. Legg merke til at leseren ikke er lukket i den gitte koden.

Vi kan gjøre det ved å bruke en finalizer:

@ Override public void finalize () {prøv {reader.close (); System.out.println ("Closed BufferedReader in the finalizer"); } fange (IOException e) {// ...}}

Det er lett å se at en finalizer blir erklært akkurat som en hvilken som helst vanlig forekomstmetode.

I virkeligheten, tidspunktet som søppeloppsamleren ringer sluttbehandlere, er avhengig av JVMs implementering og systemets forhold, som er utenfor vår kontroll.

For å få søppeloppsamling til å skje på stedet, vil vi dra nytte av System.gc metode. I virkelige systemer skal vi aldri påberope oss det eksplisitt av flere grunner:

  1. Det er kostbart
  2. Det utløser ikke søppeloppsamlingen umiddelbart - det er bare et hint for JVM å starte GC
  3. JVM vet bedre når GC må ringes

Hvis vi trenger å tvinge GC, kan vi bruke jconsole for det.

Følgende er en prøvesak som demonstrerer driften av en sluttbehandler:

@Test offentlig ugyldig når GC_thenFinalizerExecuted () kaster IOException {String firstLine = new Finalizable (). ReadFirstLine (); assertEquals ("baeldung.com", firstLine); System.gc (); }

I den første uttalelsen, a Endelig objektet er opprettet, så er det readFirstLine metoden kalles. Dette objektet er ikke tildelt noen variabel, og det er derfor kvalifisert for søppeloppsamling når System.gc metoden påberopes.

Påstanden i testen verifiserer innholdet i inndatafilen og brukes bare for å bevise at vår tilpassede klasse fungerer som forventet.

Når vi kjører den medfølgende testen, vil det bli skrevet ut en melding på konsollen om at den bufrede leseren lukkes i ferdigbehandleren. Dette innebærer at fullføre metoden ble kalt, og den har ryddet opp i ressursen.

Fram til dette punktet ser sluttbehandlerne ut som en flott måte å pre-ødelegge operasjoner på. Det er imidlertid ikke helt sant.

I neste avsnitt vil vi se hvorfor bruk av dem bør unngås.

3. Unngå sluttbehandlere

Til tross for fordelene de bringer med seg, har finaliserer mange ulemper.

3.1. Ulemper med Finalizers

La oss se på flere problemer vi vil møte når vi bruker sluttbehandlere til å utføre kritiske handlinger.

Det første merkbare problemet er mangelen på hurtighet. Vi kan ikke vite når en finalizer kjører, siden søppel kan samles når som helst.

I seg selv er dette ikke et problem fordi finalisatoren fortsatt kjøres før eller senere. Systemressurser er imidlertid ikke ubegrenset. Dermed kan vi gå tom for ressurser før en opprydding skjer, noe som kan føre til et systemkrasj.

Finaliserer har også innvirkning på programmets bærbarhet. Siden søppelinnsamlingsalgoritmen er avhengig av JVM-implementering, kan et program kjøre veldig bra på ett system mens det oppfører seg annerledes på et annet.

Ytelseskostnaden er et annet viktig problem som følger med sluttbehandlere. Nærmere bestemt, JVM må utføre mange flere operasjoner når de konstruerer og ødelegger objekter som inneholder en ikke-tom finalisator.

Det siste problemet vi skal snakke om er mangelen på unntakshåndtering under ferdigstillelse. Hvis en sluttbehandler kaster et unntak, stopper sluttføringsprosessen og etterlater objektet i en ødelagt tilstand uten varsel.

3.2. Demonstrasjon av finaliseringseffekter

Det er på tide å legge teorien til side og se effekten av finalisering i praksis.

La oss definere en ny klasse med en ikke-tom finalizer:

offentlig klasse CrashedFinalizable {public static void main (String [] args) kaster ReflectiveOperationException {for (int i = 0;; i ++) {new CrashedFinalizable (); // annen kode}} @ Override-beskyttet tomrom finalize () {System.out.print (""); }}

Legg merke til fullfør () metode - den skriver bare ut en tom streng til konsollen. Hvis denne metoden var helt tom, ville JVM behandle objektet som om det ikke hadde en finalizer. Derfor må vi tilby fullføre () med en implementering, som nesten ikke gjør noe i dette tilfellet.

Inne i hoved- metode, en ny CrashedFinaliserbar forekomst blir opprettet i hver iterasjon av til Løkke. Denne forekomsten er ikke tildelt noen variabel, og dermed kvalifisert for søppeloppsamling.

La oss legge til noen utsagn på linjen merket med // annen kode for å se hvor mange objekter som finnes i minnet ved kjøretid:

hvis ((i% 1_000_000) == 0) {Class finalizerClass = Class.forName ("java.lang.ref.Finalizer"); Felt queueStaticField = finalizerClass.getDeclaredField ("kø"); queueStaticField.setAccessible (true); ReferenceQueue referenceQueue = (ReferenceQueue) queueStaticField.get (null); Field queueLengthField = ReferenceQueue.class.getDeclaredField ("queLength"); queueLengthField.setAccessible (true); long queueLength = (long) queueLengthField.get (referenceQueue); System.out.format ("Det er% d referanser i køen% n", queLength); }

De gitte uttalelsene får tilgang til noen felt i interne JVM-klasser og skriver ut antall objektreferanser etter hver million iterasjoner.

La oss starte programmet ved å utføre hoved- metode. Vi kan forvente at det går på ubestemt tid, men det er ikke tilfelle. Etter noen minutter bør vi se systemet krasje med en feil som denne:

... Det er 21914844 referanser i køen Det er 22858923 referanser i køen Det er 24202629 referanser i køen Det er 24621725 referanser i køen Det er 25410983 referanser i køen Det er 26231621 referanser i køen Det er 26975913 referanser i køen køen Unntak i tråden "hoved" java.lang.OutOfMemoryError: GC overheadgrense overskredet ved java.lang.ref.Finalizer.register (Finalizer.java:91) ved java.lang.Object. (Object.java:37) kl. com.baeldung.finalize.CrashedFinalizable. (CrashedFinalizable.java:6) på com.baeldung.finalize.CrashedFinalizable.main (CrashedFinalizable.java:9) Prosess ferdig med utgangskode 1

Ser ut som søppeloppsamleren ikke gjorde jobben sin bra - antall gjenstander økte til systemet krasjet.

Hvis vi fjernet finalisatoren, ville antall referanser vanligvis være 0 og programmet fortsette å kjøre for alltid.

3.3. Forklaring

For å forstå hvorfor søppeloppsamleren ikke kastet gjenstander som den skal, må vi se på hvordan JVM fungerer internt.

Når du oppretter et objekt, også kalt referent, som har en sluttbehandler, oppretter JVM et tilhørende referanseobjekt av typen java.lang.ref.Finalizer. Etter at referenten er klar for søppelinnsamling, markerer JVM referanseobjektet som klart for behandling og setter det i en referansekø.

Vi får tilgang til denne køen via det statiske feltet i java.lang.ref.Finalizer klasse.

I mellomtiden ringte en spesiell daemon-tråd Finaliser fortsetter å løpe og ser etter objekter i referansekøen. Når den finner en, fjerner den referanseobjektet fra køen og kaller sluttbehandleren på referenten.

I løpet av neste søppelsamlingssyklus vil referenten bli kastet - når det ikke lenger refereres til det fra et referanseobjekt.

Hvis en tråd fortsetter å produsere gjenstander i høy hastighet, er det som skjedde i vårt eksempel, Finaliser tråden kan ikke følge med. Til slutt vil ikke minnet kunne lagre alle objektene, og vi ender opp med en OutOfMemoryError.

Legg merke til en situasjon der gjenstander blir opprettet med varpshastighet som vist i dette avsnittet ikke ofte skjer i det virkelige liv. Imidlertid viser det et viktig poeng - sluttbehandlere er veldig dyre.

4. No-Finalizer Eksempel

La oss utforske en løsning som gir samme funksjonalitet, men uten bruk av fullfør () metode. Legg merke til at eksemplet nedenfor ikke er den eneste måten å erstatte finalisatorer på.

I stedet brukes det til å demonstrere et viktig poeng: det er alltid alternativer som hjelper oss å unngå sluttbehandlere.

Her er erklæringen fra den nye klassen:

offentlig klasse CloseableResource implementerer AutoCloseable {privat BufferedReader-leser; public CloseableResource () {InputStream input = this.getClass () .getClassLoader () .getResourceAsStream ("file.txt"); leser = ny BufferedReader (ny InputStreamReader (input)); } offentlig String readFirstLine () kaster IOException {String firstLine = reader.readLine (); returner firstLine; } @ Overstyr offentlig tomrom lukk () {prøv {reader.close (); System.out.println ("Closed BufferedReader in the close method"); } fange (IOException e) {// håndtere unntak}}}

Det er ikke vanskelig å se at den eneste forskjellen mellom den nye CloseableResource klasse og vår forrige Endelig klasse er implementeringen av Kan lukkes automatisk grensesnitt i stedet for definisjon av sluttbehandler.

Legg merke til at kroppen til Lukk Metode av CloseableResource er nesten det samme som kroppen til finalisatoren i klassen Endelig.

Følgende er en testmetode som leser en inndatafil og frigjør ressursen etter endt jobb:

@Test offentlig ugyldig når TryWResourcesExits_thenResourceClosed () kaster IOException {try (CloseableResource resource = new CloseableResource ()) {String firstLine = resource.readFirstLine (); assertEquals ("baeldung.com", firstLine); }}

I testen ovenfor, a CloseableResource forekomst opprettes i prøve blokkering av en prøve-med-ressurserklæring, derav at ressursen automatisk lukkes når prøve-med-ressurser-blokken fullfører kjøringen.

Når du kjører den gitte testmetoden, ser vi en melding skrevet ut fra Lukk metoden for CloseableResource klasse.

5. Konklusjon

I denne opplæringen fokuserte vi på et kjernekonsept i Java - the fullføre metode. Dette ser bra ut på papir, men kan ha stygge bivirkninger ved kjøretid. Og enda viktigere, det er alltid en alternativ løsning å bruke en finalizer.

Et viktig poeng å merke er at fullføre er avviklet fra og med Java 9 - og vil til slutt bli fjernet.

Som alltid kan kildekoden for denne opplæringen bli funnet på GitHub.


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