Sprievodca dokončením metódy v prostredí Java

1. Prehľad

V tomto výučbe sa zameriame na základný aspekt jazyka Java - jazyk dokončiť metóda, ktorú poskytuje root Objekt trieda.

Jednoducho povedané, toto sa volá pred zberom odpadu pre konkrétny objekt.

2. Používanie Finalizátorov

The dokončiť () metóda sa nazýva finalizátor.

Finalizátory sa vyvolajú, keď JVM zistí, že táto konkrétna inštancia by sa mala zbierať odpadky. Takýto finalizátor môže vykonávať akékoľvek operácie vrátane vrátenia objektu späť do života.

Hlavným účelom finalizátora je však uvoľnenie prostriedkov používaných objektmi pred ich odstránením z pamäte. Finalizátor môže fungovať ako primárny mechanizmus pri čistiacich operáciách alebo ako bezpečnostná sieť pri zlyhaní iných metód.

Aby sme pochopili, ako finalizátor funguje, pozrime sa na vyhlásenie triedy:

public class Finalizable {private BufferedReader reader; public Finalizable () {InputStream input = this.getClass () .getClassLoader () .getResourceAsStream ("file.txt"); this.reader = nový BufferedReader (nový InputStreamReader (vstup)); } public String readFirstLine () vyvolá IOException {String firstLine = reader.readLine (); návrat firstLine; } // ďalší členovia triedy}

Trieda Dokončiteľné má pole čitateľ, ktorý odkazuje na uzatvárateľný zdroj. Keď je objekt vytvorený z tejto triedy, vytvorí nový BufferedReader napríklad čítanie zo súboru v triede.

Takýto prípad sa používa v dokumente readFirstLine metóda na extrahovanie prvého riadku v danom súbore. Všimnite si, že čítačka nie je v danom kóde uzavretá.

Môžeme to urobiť pomocou finalizátora:

@Override public void finalize () {try {reader.close (); System.out.println ("Closed BufferedReader vo finalizátore"); } chytit (IOException e) {// ...}}

Je ľahké vidieť, že finalizátor je deklarovaný ako každá iná bežná inštančná metóda.

V realite, čas, kedy smetiar volá finalizátory, závisí od implementácie JVM a od podmienok systému, ktoré sú mimo našu kontrolu.

Aby sa odvoz odpadu mohol uskutočniť na mieste, využijeme výhody System.gc metóda. V systémoch skutočného sveta by sme sa toho nikdy nemali dovolávať výslovne, z mnohých dôvodov:

  1. Je to nákladné
  2. To nespustí zber odpadu okamžite - je to len náznak pre JVM, aby spustil GC
  3. JVM vie lepšie, kedy je potrebné zavolať GC

Ak potrebujeme vynútiť GC, môžeme použiť jconsole pre to.

Nasleduje testovací prípad demonštrujúci činnosť finalizátora:

@ Test public void whenGC_thenFinalizerExecuted () hodí IOException {String firstLine = new Finalizable (). ReadFirstLine (); assertEquals ("baeldung.com", firstLine); System.gc (); }

V prvom vyjadrení a Dokončiteľné vytvorí sa objekt, potom jeho readFirstLine metóda sa volá. Tento objekt nie je priradený k žiadnej premennej, a preto je vhodný na zber odpadu, keď System.gc metóda je vyvolaná.

Tvrdenie v teste overuje obsah vstupného súboru a slúži iba na dokázanie toho, že naša vlastná trieda funguje podľa očakávania.

Keď spustíme poskytnutý test, na konzole sa vytlačí správa o zatvorení čítačky s medzipamäťou vo finalizátore. To znamená dokončiť bola zavolaná metóda, ktorá vyčistila zdroj.

Až do tohto okamihu vyzerajú finalizátory ako vynikajúci spôsob operácií pred zničením. To však nie je tak celkom pravda.

V nasledujúcej časti uvidíme, prečo sa treba vyhnúť ich používaniu.

3. Vyhýbanie sa finalizátorom

Napriek výhodám, ktoré prinášajú, majú finalizátory veľa nevýhod.

3.1. Nevýhody Finalizátorov

Pozrime sa na niekoľko problémov, ktorým budeme čeliť pri používaní finalizátorov na vykonávanie kritických akcií.

Prvým nápadným problémom je nedostatok pohotovej situácie. Nemôžeme vedieť, kedy beží finalizátor, pretože zhromažďovanie odpadu sa môže vyskytnúť kedykoľvek.

Sám o sebe to nie je problém, pretože finalizátor sa skôr či neskôr stále vykoná. Systémové zdroje však nie sú neobmedzené. Môže sa nám stať, že nám dôjdu prostriedky, skôr ako dôjde k vyčisteniu, čo môže mať za následok zlyhanie systému.

Finalizátory majú tiež vplyv na prenosnosť programu. Pretože algoritmus odvozu odpadu je závislý od implementácie JVM, program môže bežať veľmi dobre na jednom systéme, zatiaľ čo na inom sa môže správať inak.

Náklady na výkon sú ďalším významným problémom, ktorý prichádza s finalizátormi. Konkrétne JVM musí vykonať oveľa viac operácií pri konštrukcii a ničení objektov obsahujúcich neprázdny finalizátor.

Posledným problémom, o ktorom si povieme, je nedostatok spracovania výnimiek počas finalizácie. Ak finalizátor vyvolá výnimku, proces finalizácie sa zastaví a objekt bude v poškodenom stave bez akéhokoľvek oznámenia.

3.2. Ukážka účinkov finalizátorov

Je čas dať teóriu stranou a vidieť účinky finalizátorov v praxi.

Definujme novú triedu s neprázdnym finalizátorom:

public class CrashedFinalizable {public static void main (String [] args) hodí ReflectiveOperationException {for (int i = 0;; i ++) {new CrashedFinalizable (); // iný kód}} @Override protected void finalize () {System.out.print (""); }}

Všimnite si dokončiť () metóda - do konzoly iba vypíše prázdny reťazec. Keby bola táto metóda úplne prázdna, JVM by s objektom zaobchádzalo, akoby nemal finalizátor. Preto musíme poskytnúť dokončiť () s implementáciou, ktorá v tomto prípade nerobí takmer nič.

Vnútri hlavný metóda, nová HavarovanéFinalizovateľné inštancia sa vytvorí v každej iterácii súboru pre slučka. Táto inštancia nie je priradená k žiadnej premennej, a preto je vhodná na zber odpadu.

Pridajme niekoľko výrokov k riadku označenému // iný kód Ak chcete zistiť, koľko objektov existuje v pamäti za behu:

if ((i% 1_000_000) == 0) {Class finalizerClass = Class.forName ("java.lang.ref.Finalizer"); Field queueStaticField = finalizerClass.getDeclaredField ("fronta"); queueStaticField.setAccessible (true); ReferenceQueue referenceQueue = (ReferenceQueue) queueStaticField.get (null); Field queueLengthField = ReferenceQueue.class.getDeclaredField ("queueLength"); queueLengthField.setAccessible (true); long queueLength = (long) queueLengthField.get (referenceQueue); System.out.format ("Vo fronte% n je% d referencií, queueLength); }

Dané príkazy pristupujú k niektorým poliam v interných triedach JVM a tlačia počet odkazov na objekty po každých miliónoch iterácií.

Začnime program vykonaním hlavný metóda. Môžeme očakávať, že bude trvať donekonečna, ale nie je to tak. Po niekoľkých minútach by sme mali vidieť zlyhanie systému s chybou podobnou tejto:

... Vo fronte je 21914844 referencií Vo fronte je 22858923 referencií Vo fronte je 24202629 referencií Vo fronte je 24621725 referencií Vo fronte je 25410983 referencií Vo fronte je 26231621 referencií Existuje 26975913 referencií v front Výnimka vo vlákne "hlavný" java.lang.OutOfMemoryError: Bol prekročený limit réžie GC na java.lang.ref.Finalizer.register (Finalizer.java:91) na java.lang.Object. (Object.java:37) o com.baeldung.finalize.CrashedFinalizable. (CrashedFinalizable.java:6) na adrese com.baeldung.finalize.CrashedFinalizable.main (CrashedFinalizable.java:9) Proces ukončený výstupným kódom 1

Vyzerá to, že zberač odpadu nezvládol svoju prácu dobre - počet objektov sa neustále zvyšoval, kým nedošlo k zrúteniu systému.

Ak by sme odstránili finalizátor, počet referencií by bol zvyčajne 0 a program by bežal navždy.

3.3. Vysvetlenie

Aby sme pochopili, prečo smetiar nezlikvidoval objekty tak, ako by mal, musíme sa pozrieť na to, ako interne funguje JVM.

Pri vytváraní objektu nazývaného tiež referent, ktorý má finalizátor, vytvorí JVM sprievodný referenčný objekt typu java.lang.ref. Finalizátor. Keď je referent pripravený na odvoz odpadu, JVM označí referenčný objekt ako pripravený na spracovanie a umiestni ho do referenčného frontu.

Do tohto radu môžeme vstúpiť cez statické pole poradie v java.lang.ref. Finalizátor trieda.

Medzitým sa volalo špeciálne vlákno démona Finalizátor stále beží a vyhľadáva objekty v referenčnom fronte. Keď nejaký nájde, odstráni referenčný objekt z frontu a zavolá finalizátor na referenta.

Počas nasledujúceho cyklu zberu odpadu bude referent zahodený - keď už nebude odkazovaný z referenčného objektu.

Ak vlákno stále produkuje objekty vysokou rýchlosťou, čo sa stalo v našom príklade, znak Finalizátor vlákno nestíha. Nakoniec pamäť nebude schopná uložiť všetky objekty a nakoniec skončíme znakom OutOfMemoryError.

Všimnite si, že situácia, keď sú objekty vytvárané warpovou rýchlosťou, ako je uvedené v tejto časti, sa v reálnom živote často nestáva. Ukazuje však dôležitý bod - finalizátory sú veľmi drahé.

4. Príklad bez finalizátora

Poďme preskúmať riešenie poskytujúce rovnakú funkcionalitu, ale bez použitia dokončiť () metóda. Všimnite si, že príklad uvedený nižšie nie je jediný spôsob, ako nahradiť finalizátory.

Namiesto toho sa používa na preukázanie dôležitého bodu: vždy existujú možnosti, ktoré nám pomôžu vyhnúť sa finalizátorom.

Tu je vyhlásenie našej novej triedy:

verejná trieda CloseableResource implementuje AutoCloseable {súkromná čítačka BufferedReader; public CloseableResource () {InputStream vstup = this.getClass () .getClassLoader () .getResourceAsStream ("file.txt"); čítačka = nový BufferedReader (nový InputStreamReader (vstup)); } public String readFirstLine () vyvolá IOException {String firstLine = reader.readLine (); návrat firstLine; } @Override public void close () {try {reader.close (); System.out.println ("Closed BufferedReader v metóde close"); } chytit (IOException e) {// spracovat vynimku}}}

Nie je ťažké vidieť jediný rozdiel medzi novými CloseableResource triedy a náš predchádzajúci Dokončiteľné triedy je implementácia Automatické uzatváranie namiesto definície finalizátora.

Všimnite si, že telo Zavrieť metóda CloseableResource je takmer rovnaké ako telo finalizátora v triede Dokončiteľné.

Nasleduje testovacia metóda, ktorá načíta vstupný súbor a po dokončení úlohy uvoľní zdroj:

@Test public void when TryWResourcesExits_thenResourceClosed () throws IOException {try (CloseableResource resource = new CloseableResource ()) {String firstLine = resource.readFirstLine (); assertEquals ("baeldung.com", firstLine); }}

Vo vyššie uvedenom teste a CloseableResource inštancia je vytvorená v skús blok príkazu try-with-resources, preto sa tento zdroj automaticky uzavrie, keď blok try-with-resources dokončí vykonávanie.

Po spustení danej testovacej metódy uvidíme správu vytlačenú z Zavrieť metóda CloseableResource trieda.

5. Záver

V tomto tutoriáli sme sa zamerali na základný koncept v Jave - dokončiť metóda. Na papieri to vyzerá užitočne, ale za behu môže mať škaredé vedľajšie účinky. A čo je dôležitejšie, k použitiu finalizátora vždy existuje alternatívne riešenie.

Jedným kritickým bodom, ktorý si treba všimnúť, je to dokončiť bola ukončená od verzie Java 9 - a nakoniec bude odstránená.

Ako vždy, zdrojový kód tohto tutoriálu nájdete na GitHub.


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