Perché il metodo finalize è incluso in Java?


44

Secondo questo post , non dovremmo mai fare affidamento sul metodo finalize da chiamare. Allora perché Java l'ha incluso nel linguaggio di programmazione?

Sembra una decisione terribile includere in qualsiasi linguaggio di programmazione una funzione che potrebbe essere chiamata.

Risposte:


41

Secondo Effective Java (Seconda Edizione) di Joshua Bloch, ci sono due scenari in cui finalize()è utile:

  1. Uno è agire come una "rete di sicurezza" nel caso in cui il proprietario di un oggetto dimentichi di chiamarne il metodo di terminazione esplicita. Sebbene non vi sia alcuna garanzia che il finalizzatore verrà invocato tempestivamente, potrebbe essere meglio liberare la risorsa in ritardo che mai, in quei casi (si spera rari) in cui il client non riesce a chiamare il metodo di terminazione esplicita. Ma il finalizzatore dovrebbe registrare un avviso se rileva che la risorsa non è stata terminata

  2. Un secondo uso legittimo dei finalizzatori riguarda gli oggetti con peer nativi. Un peer nativo è un oggetto nativo al quale un oggetto normale delega tramite metodi nativi. Poiché un peer nativo non è un oggetto normale, il garbage collector non lo conosce e non può recuperarlo quando il suo peer Java viene recuperato. Un finalizzatore è un veicolo appropriato per eseguire questa attività, supponendo che il peer nativo non disponga di risorse critiche. Se il peer nativo contiene risorse che devono essere terminate prontamente, la classe dovrebbe avere un metodo di terminazione esplicito, come descritto sopra. Il metodo di terminazione dovrebbe fare tutto il necessario per liberare la risorsa critica.

Per ulteriori letture, consultare l'articolo 7, pagina 27.


4
Con # 2 sembra che il peer nativo sia una risorsa nativa che richiede una rete di sicurezza e dovrebbe avere un metodo di terminazione, e quindi non dovrebbe essere un punto separato.
Mooing Duck,

2
@MooingDuck: # 2 è un punto separato perché, se il peer nativo non contiene risorse critiche (ad es. È puramente un oggetto in memoria), non è necessario esporre un metodo di terminazione esplicita. La rete di sicurezza n. 1 riguarda esplicitamente un metodo di terminazione.
jhominal,

55

I finalizzatori sono importanti per la gestione delle risorse native. Ad esempio, è possibile che l'oggetto debba allocare un WidgetHandle dal sistema operativo utilizzando un'API non Java. Se non rilasci WidgetHandle quando l'oggetto è GC'd, perderai WidgetHandles.

L'importante è che i casi "finalizzatore non venga mai chiamato" si rompano piuttosto semplicemente:

  1. Il programma si arresta rapidamente
  2. L'oggetto "vive per sempre" durante la durata del programma
  3. Il computer si spegne / il processo viene interrotto dal sistema operativo / ecc

In tutti e tre questi casi, o non si dispone di una perdita nativa (in virtù del fatto che il programma non è più in esecuzione) o si ha già una perdita non nativa (se si continua a allocare oggetti gestiti senza che siano GC'd).

L'avvertimento "non fare affidamento sul finalizzatore chiamato" riguarda in realtà il non utilizzo dei finalizzatori per la logica del programma. Ad esempio, non vuoi tenere traccia di quanti dei tuoi oggetti esistono in tutte le istanze del tuo programma incrementando un contatore in un file da qualche parte durante la costruzione e decrementandolo in un finalizzatore - perché non c'è garanzia che i tuoi oggetti essere finalizzato, questo contatore di file probabilmente non tornerà mai a 0. Questo è davvero un caso speciale del principio più generale che non dovresti dipendere dal fatto che il tuo programma si chiuda normalmente (interruzioni di corrente, ecc.).

Per la gestione delle risorse native, tuttavia, i casi in cui il finalizzatore non viene eseguito corrispondono ai casi in cui non ti interessa se non viene eseguito.


Risposta eccellente. Ero come se potesse essere chiamato .. dovrebbe essere comunque incluso. Sono stato confuso per un po 'con la domanda e quella collegata su StackOverflow.
Informato il

3
Non era nella domanda, ma vale la pena discutere nel contesto di "non fare affidamento sul finalizzatore chiamato": il finalizzatore può essere chiamato, ma solo dopo un ritardo arbitrariamente lungo dopo che l'oggetto diventa irraggiungibile. Questo è importante per le "risorse native" che sono molto più scarse della memoria (che sono la maggior parte di esse, si scopre).

26
"I finalizzatori sono importanti per la gestione delle risorse native." - nooooooooo, scusa ahem. L'utilizzo di finalizzatori per la gestione delle risorse native è un'idea orribile (motivo per cui ora è autocloseable e co). Il GC si preoccupa solo della memoria, non di qualsiasi altra risorsa. Ciò significa che puoi esaurire le risorse native ma hai ancora memoria sufficiente per non attivare un GC - non lo vogliamo davvero. Anche i finalizzatori rendono GC più costoso, il che non è una buona idea.
Voo,

12
Sono d'accordo si dovrebbe comunque avere una strategia di gestione delle risorse native esplicito diverso finalizzatori, ma se l'oggetto fa destinarli e non li liberi nel finalizzatore, si sta aprendo da soli fino a una perdita nativo nel caso in cui l'oggetto è GC'd senza la liberazione esplicita della risorsa. In altre parole, i finalizzatori sono una parte importante di una strategia di gestione delle risorse native, non una soluzione al problema in generale.
Ryan Cavanaugh,

1
@Voo è probabilmente più corretto affermare che il finalizzatore è il fallback nel caso in cui il contratto dell'oggetto non venga seguito ( close()non viene mai chiamato prima che diventi irraggiungibile)
maniaco del cricchetto

5

Lo scopo di questo metodo è spiegato nella documentazione API come segue:

viene invocato se e quando la macchina virtuale Java ha determinato che non è più possibile accedere a questo oggetto da alcun thread che non è ancora morto, tranne a seguito di un'azione intrapresa dalla finalizzazione di qualche altro oggetto o classe che è pronta per essere finalizzata ...

il solito scopo di finalize... è eseguire azioni di pulizia prima che l'oggetto venga irrevocabilmente scartato . Ad esempio, il metodo finalize per un oggetto che rappresenta una connessione input / output potrebbe eseguire transazioni I / O esplicite per interrompere la connessione prima che l'oggetto venga scartato in modo permanente ...


Se sei inoltre interessato ai motivi per cui i progettisti linguistici hanno scelto che "l'oggetto viene irrevocabilmente scartato" ( garbage collection ) il modo che va oltre il controllo del programmatore dell'applicazione ("non dovremmo mai fare affidamento"), questo è stato spiegato in una risposta a domanda :

garbage collection automatica ... elimina intere classi di errori di programmazione che disturbano i programmatori C e C ++. È possibile sviluppare il codice Java con la certezza che il sistema troverà rapidamente molti errori e che i problemi principali rimarranno inattivi fino a quando il codice di produzione non sarà stato spedito.

La citazione sopra, a sua volta, è stata presa dalla documentazione ufficiale sugli obiettivi di progettazione di Java , ovvero può essere considerata un riferimento autorevole che spiega perché i progettisti del linguaggio Java hanno deciso in questo modo.

Per una discussione agnostica più dettagliata e linguistica di questa preferenza, consultare la sezione 9.6 della OOSC Gestione automatica della memoria (in realtà, non solo questa sezione, ma vale la pena leggere l'intero capitolo 9 se siete interessati a cose del genere). Questa sezione si apre con un'affermazione inequivocabile:

Un buon ambiente OO dovrebbe offrire un meccanismo automatico di gestione della memoria che rileverà e recupererà oggetti non raggiungibili, consentendo agli sviluppatori di applicazioni di concentrarsi sul proprio lavoro - sviluppo di applicazioni.

La discussione precedente dovrebbe essere sufficiente per dimostrare quanto sia importante disporre di una tale struttura disponibile. Nelle parole di Michael Schweitzer e Lambert Strether:

Un programma orientato agli oggetti senza gestione automatica della memoria è all'incirca lo stesso di una pentola a pressione senza valvola di sicurezza: prima o poi la cosa esploderà sicuramente!


4

I finalizzatori esistono perché ci si aspettava che fossero un mezzo efficace per garantire che le cose venissero ripulite (anche se in pratica non lo sono) e perché quando sono state inventate, mezzi migliori per garantire la pulizia (come riferimenti fantasma e prova con -resources) non esisteva ancora. Con il senno di poi, Java sarebbe probabilmente migliore se lo sforzo speso per implementare la sua struttura di "finalizzazione" fosse stato speso per altri mezzi di pulizia, ma questo non era chiaro durante il periodo in cui Java era inizialmente sviluppato.


3

CAVEAT: Potrei essere obsoleto, ma questa è la mia comprensione di qualche anno fa:

In generale, non vi è alcuna garanzia di quando viene eseguito un finalizzatore - o addirittura che funzioni affatto, anche se alcune JVM ti permetteranno di richiedere un GC completo e la finalizzazione prima che il programma termini (il che, ovviamente, significa che il programma impiega più tempo per uscire e che non è la modalità operativa predefinita).

E alcuni GC erano noti per ritardare o evitare esplicitamente gli oggetti GC che avevano finalizzatori, nella speranza che ciò producesse migliori prestazioni sui benchmark.

Sfortunatamente, questi comportamenti sono in conflitto con le ragioni originali per cui sono stati raccomandati i finalizzatori e hanno invece incoraggiato l'uso di metodi di spegnimento esplicitamente chiamati.

Se hai un oggetto che deve davvero essere ripulito prima di essere scartato, e se davvero non puoi fidarti degli utenti per farlo, un finalizzatore può comunque valere la pena considerare. Ma in generale, ci sono buoni motivi per cui non li vedi così spesso nel moderno codice Java come in alcuni dei primi esempi.


1

La Guida allo stile di Google Java ha alcuni saggi consigli sull'argomento:

È estremamente raro ignorare Object.finalize.

Suggerimento : non farlo. Se assolutamente necessario, leggi e capisci prima l' Efficace Java Item 7, "Evita i finalizzatori", con molta attenzione, quindi non farlo.


5
Perché ammiro l'eccessiva semplificazione del problema, "non farlo" non è una risposta a "perché esiste".
Pierre Arlaud,

1
Immagino di non averlo spiegato bene nella mia risposta, ma (IMO) la risposta a "perché esiste?" è "non dovrebbe". Per quei rari casi in cui hai davvero bisogno di un'operazione di finalizzazione, probabilmente vorrai crearla tu stesso PhantomReferencee ReferenceQueueinvece.
Daniel Pryden,

Intendevo dire * ovviamente, sebbene non ti abbia votato in negativo. La risposta più votata mostra comunque quanto sia utile il metodo, un po 'invalidando il tuo punto.
Pierre Arlaud,

1
Anche nel caso dei peer nativi, penso che PhantomReferencesia una soluzione migliore. I finalizzatori sono una verruca rimasta fin dai primi giorni di Java Object.clone()e tipi simili e grezzi fanno parte del linguaggio meglio dimenticato.
Daniel Pryden,

1
I pericoli dei finalizzatori sono descritti in modo più accessibile (comprensibile dagli sviluppatori) in C #. Tuttavia, non si deve sottintendere che i meccanismi sottostanti su Java e C # siano gli stessi; non c'è nulla nelle loro specifiche che lo affermino.
rwong

1

Le specifiche del linguaggio Java (Java SE 7) indicano:

I finalizzatori offrono la possibilità di liberare risorse che non possono essere liberate automaticamente da un gestore di archiviazione automatico. In tali situazioni, il semplice recupero della memoria utilizzata da un oggetto non garantirebbe il recupero delle risorse detenute.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.