Een gids voor de afrondingsmethode in Java

1. Overzicht

In deze zelfstudie zullen we ons concentreren op een kernaspect van de Java-taal: het afronden methode geleverd door de root Voorwerp klasse.

Simpel gezegd, dit wordt genoemd vóór de garbagecollection voor een bepaald object.

2. Finalizers gebruiken

De afronden() methode heet de finalizer.

Finalizers worden aangeroepen wanneer JVM erachter komt dat deze specifieke instantie met afval moet worden opgehaald. Zo'n finalizer kan alle bewerkingen uitvoeren, inclusief het weer tot leven brengen van het object.

Het belangrijkste doel van een finalizer is echter om bronnen vrij te geven die door objecten worden gebruikt voordat ze uit het geheugen worden verwijderd. Een finalisator kan werken als het primaire mechanisme voor opruimoperaties of als vangnet wanneer andere methoden falen.

Om te begrijpen hoe een finalizer werkt, laten we eens kijken naar een klasseverklaring:

openbare klasse Finalizable {privé BufferedReader-lezer; openbare Finalizable () {InputStream input = this.getClass () .getClassLoader () .getResourceAsStream ("file.txt"); this.reader = nieuwe BufferedReader (nieuwe InputStreamReader (input)); } public String readFirstLine () gooit IOException {String firstLine = reader.readLine (); terugkeer firstLine; } // andere klasleden}

De klas Finaliseerbaar heeft een veld lezer, die verwijst naar een afsluitbare bron. Wanneer een object uit deze klasse wordt gemaakt, construeert het een nieuw BufferedReader instantie lezen uit een bestand in het klassenpad.

Zo'n exemplaar wordt gebruikt in de readFirstLine methode om de eerste regel in het opgegeven bestand uit te pakken. Merk op dat de lezer niet is gesloten in de opgegeven code.

We kunnen dat doen met behulp van een finalizer:

@Override public void finalize () {probeer {reader.close (); System.out.println ("Gesloten BufferedReader in de finalizer"); } catch (IOException e) {// ...}}

Het is gemakkelijk te zien dat een finalizer wordt gedeclareerd, net als elke normale instantiemethode.

In werkelijkheid, het moment waarop de garbage collector finalizers oproept, is afhankelijk van de implementatie van de JVM en de systeemcondities, die buiten onze controle liggen.

Om ervoor te zorgen dat afval ter plaatse wordt opgehaald, maken we gebruik van de System.gc methode. In real-world systemen mogen we dat nooit expliciet aanroepen, om een ​​aantal redenen:

  1. Het is duur
  2. Het activeert de garbage collection niet onmiddellijk - het is slechts een hint voor de JVM om GC te starten
  3. JVM weet beter wanneer GC moet worden gebeld

Als we GC moeten forceren, kunnen we gebruiken jconsole daarom.

Het volgende is een testcase die de werking van een finalizer laat zien:

@ Test openbare leegte whenGC_thenFinalizerExecuted () gooit IOException {String firstLine = nieuwe Finalizable (). ReadFirstLine (); assertEquals ("baeldung.com", firstLine); System.gc (); }

In de eerste verklaring, a Finaliseerbaar object is gemaakt, dan zijn readFirstLine methode wordt genoemd. Dit object is aan geen enkele variabele toegewezen, daarom komt het in aanmerking voor garbagecollection wanneer de System.gc methode wordt aangeroepen.

De bewering in de test verifieert de inhoud van het invoerbestand en wordt alleen gebruikt om te bewijzen dat onze aangepaste klasse werkt zoals verwacht.

Wanneer we de meegeleverde test uitvoeren, wordt er een bericht op de console afgedrukt dat de gebufferde lezer wordt gesloten in de finalizer. Dit impliceert de afronden methode is aangeroepen en het heeft de bron opgeruimd.

Tot nu toe zien finalizers er uit als een uitstekende manier voor pre-destroy operaties. Dat is echter niet helemaal waar.

In de volgende sectie zullen we zien waarom het gebruik ervan moet worden vermeden.

3. Finalizers vermijden

Ondanks de voordelen die ze opleveren, hebben finalizers veel nadelen.

3.1. Nadelen van Finalizers

Laten we eens kijken naar verschillende problemen die we tegenkomen bij het gebruik van finalizers om kritieke acties uit te voeren.

Het eerste opvallende probleem is het gebrek aan snelheid. We kunnen niet weten wanneer een finalizer wordt uitgevoerd, aangezien garbagecollection op elk moment kan plaatsvinden.

Op zich is dit geen probleem omdat de finalizer vroeg of laat nog steeds wordt uitgevoerd. Systeembronnen zijn echter niet onbeperkt. Het is dus mogelijk dat onze bronnen opraken voordat er een opruimactie plaatsvindt, wat kan resulteren in een systeemcrash.

Finalizers hebben ook invloed op de overdraagbaarheid van het programma. Omdat het garbage collection-algoritme JVM-implementatie-afhankelijk is, kan een programma heel goed werken op het ene systeem, terwijl het zich anders gedraagt ​​op een ander.

De prestatiekosten zijn een ander belangrijk probleem dat wordt geleverd met finalizers. Specifiek, JVM moet veel meer bewerkingen uitvoeren bij het construeren en vernietigen van objecten die een niet-lege finalizer bevatten.

Het laatste probleem waar we het over hebben, is het gebrek aan afhandeling van uitzonderingen tijdens het afronden. Als een finalizer een uitzondering genereert, stopt het voltooiingsproces, waardoor het object in een beschadigde staat blijft zonder enige kennisgeving.

3.2. Demonstratie van de effecten van Finalizers

Het is tijd om de theorie opzij te zetten en de effecten van finalizers in de praktijk te zien.

Laten we een nieuwe klasse definiëren met een niet-lege finalizer:

openbare klasse CrashedFinalizable {openbare statische leegte hoofd (String [] args) gooit ReflectiveOperationException {voor (int i = 0;; i ++) {nieuwe CrashedFinalizable (); // andere code}} @Override beschermde leegte finalize () {System.out.print (""); }}

Let op de afronden() methode - het drukt gewoon een lege string af naar de console. Als deze methode helemaal leeg was, zou de JVM het object behandelen alsof het geen finalizer had. Daarom moeten we voorzien afronden() met een implementatie, die in dit geval bijna niets doet.

Binnen in de hoofd methode, een nieuwe Gecrasht Definieerbaar instantie wordt gemaakt in elke iteratie van het voor lus. Deze instantie is aan geen enkele variabele toegewezen en komt daarom in aanmerking voor garbagecollection.

Laten we een paar uitspraken toevoegen op de regel die is gemarkeerd met // andere code om te zien hoeveel objecten er tijdens runtime in het geheugen aanwezig zijn:

if ((i% 1_000_000) == 0) {Class finalizerClass = Class.forName ("java.lang.ref.Finalizer"); Veld queueStaticField = finalizerClass.getDeclaredField ("wachtrij"); queueStaticField.setAccessible (true); ReferenceQueue referenceQueue = (ReferenceQueue) queueStaticField.get (null); Veld queueLengthField = ReferenceQueue.class.getDeclaredField ("queueLength"); queueLengthField.setAccessible (true); lange queueLength = (lange) queueLengthField.get (referenceQueue); System.out.format ("Er zijn% d referenties in de wachtrij% n", queueLength); }

De gegeven instructies hebben toegang tot enkele velden in interne JVM-klassen en drukken het aantal objectreferenties af na elke miljoen iteraties.

Laten we het programma starten door het hoofd methode. We mogen verwachten dat het voor onbepaalde tijd loopt, maar dat is niet het geval. Na een paar minuten zouden we het systeem moeten zien crashen met een fout die lijkt op deze:

... Er zijn 21914844 referenties in de wachtrij Er zijn 22858923 referenties in de wachtrij Er zijn 24202629 referenties in de wachtrij Er zijn 24621725 referenties in de wachtrij Er zijn 25410983 referenties in de wachtrij Er zijn 26231621 referenties in de wachtrij Er zijn 26975913 referenties in de wachtrij Uitzondering in thread "main" java.lang.OutOfMemoryError: GC overheadlimiet overschreden op java.lang.ref.Finalizer.register (Finalizer.java:91) op java.lang.Object. (Object.java:37) op com.baeldung.finalize.CrashedFinalizable. (CrashedFinalizable.java:6) op com.baeldung.finalize.CrashedFinalizable.main (CrashedFinalizable.java:9) Proces voltooid met afsluitcode 1

Het lijkt erop dat de garbage collector zijn werk niet goed heeft gedaan - het aantal objecten bleef toenemen totdat het systeem crashte.

Als we de finalizer zouden verwijderen, zou het aantal referenties meestal 0 zijn en zou het programma voor altijd blijven draaien.

3.3. Uitleg

Om te begrijpen waarom de garbage collector objecten niet weggooide zoals het zou moeten, moeten we kijken hoe de JVM intern werkt.

Bij het maken van een object, ook wel referent genoemd, dat een finalizer heeft, maakt de JVM een bijbehorend referentieobject van het type java.lang.ref.Finalizer. Nadat de referent klaar is voor garbage collection, markeert de JVM het referentieobject als gereed voor verwerking en plaatst het in een referentiewachtrij.

We hebben toegang tot deze wachtrij via het statische veld wachtrij in de java.lang.ref.Finalizer klasse.

Ondertussen wordt een speciale daemon-thread genoemd Finalizer blijft draaien en zoekt naar objecten in de referentiewachtrij. Wanneer het er een vindt, verwijdert het het referentieobject uit de wachtrij en roept het de finalizer op de referent aan.

Tijdens de volgende garbage collection-cyclus wordt de referent weggegooid - wanneer er niet langer naar wordt verwezen vanuit een referentieobject.

Als een draad met hoge snelheid objecten blijft produceren, wat is gebeurd in ons voorbeeld, is de Finalizer draad kan het niet bijhouden. Uiteindelijk kan het geheugen niet alle objecten opslaan en krijgen we een Onvoldoende geheugen fout.

Merk op dat een situatie waarin objecten met warpsnelheid worden gemaakt, zoals weergegeven in deze sectie niet vaak voorkomt in het echte leven. Het toont echter een belangrijk punt aan: finalizers zijn erg duur.

4. Voorbeeld zonder finalisator

Laten we eens kijken naar een oplossing die dezelfde functionaliteit biedt, maar zonder het gebruik van afronden() methode. Merk op dat het onderstaande voorbeeld niet de enige manier is om finalizers te vervangen.

In plaats daarvan wordt het gebruikt om een ​​belangrijk punt aan te tonen: er zijn altijd opties die ons helpen om finalizers te vermijden.

Hier is de verklaring van onze nieuwe klasse:

openbare klasse CloseableResource implementeert AutoCloseable {privé BufferedReader-lezer; openbare CloseableResource () {InputStream input = this.getClass () .getClassLoader () .getResourceAsStream ("file.txt"); reader = nieuwe BufferedReader (nieuwe InputStreamReader (input)); } public String readFirstLine () gooit IOException {String firstLine = reader.readLine (); terugkeer firstLine; } @Override public void close () {probeer {reader.close (); System.out.println ("Gesloten BufferedReader in de close-methode"); } catch (IOException e) {// handle exception}}}

Het is niet moeilijk om te zien dat het enige verschil tussen de nieuwe Afsluitbare bron klasse en onze vorige Finaliseerbaar class is de implementatie van de AutoCloseable interface in plaats van een finalizer-definitie.

Merk op dat de body van de dichtbij methode van Afsluitbare bron is bijna hetzelfde als de body van de finalizer in de klas Finaliseerbaar.

Het volgende is een testmethode, die een invoerbestand leest en de bron vrijgeeft nadat het zijn taak heeft voltooid:

@ Test openbare leegte whenTryWResourcesExits_thenResourceClosed () gooit IOException {probeer (CloseableResource resource = nieuwe CloseableResource ()) {String firstLine = resource.readFirstLine (); assertEquals ("baeldung.com", firstLine); }}

In de bovenstaande test, a Afsluitbare bron instantie wordt gemaakt in het proberen blok van een try-with-resources-instructie, vandaar dat die resource automatisch wordt gesloten wanneer het try-with-resources-blok de uitvoering voltooit.

Als we de gegeven testmethode uitvoeren, zien we een bericht afgedrukt vanuit het dichtbij methode van de Afsluitbare bron klasse.

5. Conclusie

In deze tutorial hebben we ons gericht op een kernconcept in Java: het afronden methode. Dit ziet er op papier handig uit, maar kan tijdens de uitvoering lelijke bijwerkingen hebben. En, nog belangrijker, er is altijd een alternatieve oplossing voor het gebruik van een finalizer.

Een cruciaal punt om op te merken is dat afronden is verouderd vanaf Java 9 - en zal uiteindelijk worden verwijderd.

Zoals altijd is de broncode voor deze tutorial te vinden op GitHub.


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