¿Por qué implementaría alguna vez finalize ()?


371

He estado leyendo muchas de las preguntas de Java para novatos finalize()y me parece desconcertante que nadie haya dejado en claro que finalize () es una forma poco confiable de limpiar recursos. Vi a alguien comentar que lo usan para limpiar Connections, lo cual es realmente aterrador ya que la única forma de acercarse a una garantía de que una Conexión está cerrada es implementar try (catch) finalmente.

No fui educado en CS, pero he estado programando en Java profesionalmente durante casi una década y nunca he visto a nadie implementarlo finalize()en un sistema de producción. Esto todavía no significa que no tiene sus usos, o que las personas con las que he trabajado lo han estado haciendo bien.

Entonces, mi pregunta es, ¿qué casos de uso hay para implementar finalize()que no puedan manejarse de manera más confiable a través de otro proceso o sintaxis dentro del lenguaje?

Proporcione escenarios específicos o su experiencia, simplemente repitiendo un libro de texto Java o finalizando el uso previsto no es suficiente, ya que no es la intención de esta pregunta.



2
La mayoría de los códigos de aplicaciones y bibliotecas nunca se usarán finalize(). Sin embargo, el código de la biblioteca de la plataforma , como SocketInputStream, que gestiona los recursos nativos en nombre de la persona que llama, lo hace para tratar de minimizar el riesgo de pérdidas de recursos (o utiliza mecanismos equivalentes, como los PhantomReferenceque se agregaron más adelante). Por lo tanto, el ecosistema los necesita , aunque el 99.9999% de los desarrolladores nunca escribirán uno.
Brian Goetz

Respuestas:


230

Puede usarlo como respaldo para un objeto que contiene un recurso externo (socket, archivo, etc.). Implemente un close()método y documento al que deba llamarse.

Implemente finalize()para realizar el close()procesamiento si detecta que no se ha realizado. Tal vez con algo tirado para stderrseñalar que estás limpiando después de una llamada con errores.

Proporciona seguridad adicional en una situación excepcional / con errores. No todas las personas que llaman van a hacer lo correcto try {} finally {}cada vez. Desafortunado, pero cierto en la mayoría de los entornos.

Estoy de acuerdo en que rara vez se necesita. Y como señalan los comentaristas, viene con una sobrecarga de GC. Úselo solo si necesita esa seguridad de "cinturón y tirantes" en una aplicación de larga duración.

Veo que a partir de Java 9, Object.finalize()está en desuso! Nos señalan java.lang.ref.Cleanery java.lang.ref.PhantomReferencecomo alternativas.


44
Solo asegúrese de que finalize () nunca arroje una excepción, o el recolector de basura no continuará la limpieza de ese objeto, y obtendrá una pérdida de memoria.
skaffman

17
No se garantiza que Finalize se llame, así que no cuente con que libere un recurso.
flicken

19
skaffman: no lo creo (aparte de alguna implementación de JVM con errores). Desde Object.finalize () javadoc: si el método de finalización arroja una excepción no capturada, la excepción se ignora y la finalización de ese objeto finaliza.
John M

44
Su propuesta puede ocultar los errores de las personas (no llamar cerca) durante mucho tiempo. No recomendaría hacerlo.
kohlerm el

64
De hecho, he utilizado finalizar por esta razón exacta. Heredé el código y tenía muchos errores y tenía tendencia a dejar abiertas las conexiones de la base de datos. Modificamos las conexiones para contener datos sobre cuándo y dónde se crearon y luego implementamos finalizar para registrar esta información si la conexión no se cerró correctamente. Resultó ser de gran ayuda para rastrear las conexiones no cerradas.
brainimus

173

finalize()es una pista para la JVM de que podría ser bueno ejecutar su código en un momento no especificado. Esto es bueno cuando desea que el código no se ejecute misteriosamente.

Hacer algo significativo en los finalizadores (básicamente cualquier cosa excepto el registro) también es bueno en tres situaciones:

  • desea apostar que otros objetos finalizados seguirán en un estado que el resto de su programa considera válido.
  • desea agregar un montón de código de verificación a todos los métodos de todas sus clases que tienen un finalizador, para asegurarse de que se comporten correctamente después de la finalización.
  • desea resucitar accidentalmente objetos finalizados y pasar mucho tiempo tratando de descubrir por qué no funcionan y / o por qué no se finalizan cuando finalmente se liberan.

Si cree que necesita finalizar (), a veces lo que realmente quiere es una referencia fantasma (que en el ejemplo dado podría contener una referencia sólida a una conexión utilizada por su referencia y cerrarla después de que la referencia fantasma haya sido puesta en cola). Esto también tiene la propiedad de que misteriosamente nunca se ejecutará, pero al menos no puede invocar métodos o resucitar objetos finalizados. Por lo tanto, es adecuado para situaciones en las que no es absolutamente necesario cerrar esa conexión limpiamente, pero le gustaría hacerlo, y los clientes de su clase no pueden o no llamarán a sí mismos (lo que en realidad es bastante justo: qué' ¿Es el punto de tener un recolector de basura si diseña interfaces que requieren una acción específica antes de la recolección? Eso solo nos devuelve a los días de malloc / free.)

Otras veces necesita el recurso que cree que está logrando para ser más robusto. Por ejemplo, ¿por qué necesita cerrar esa conexión? En última instancia, debe basarse en algún tipo de E / S proporcionadas por el sistema (socket, archivo, lo que sea), entonces, ¿por qué no puede confiar en que el sistema lo cierre por usted cuando el nivel más bajo de recursos está ajustado? Si el servidor en el otro extremo requiere absolutamente que cierre la conexión limpiamente en lugar de simplemente dejar caer el zócalo, entonces ¿qué sucederá cuando alguien tropiece con el cable de alimentación de la máquina en la que se está ejecutando su código o se interrumpe la red?

Descargo de responsabilidad: he trabajado en una implementación de JVM en el pasado. Odio los finalizadores.


76
Votación a favor del sarcasmo extremo justicia.
Percepción

32
Esto no responde la pregunta; solo dice todos los inconvenientes de, finalize()sin dar ninguna razón, alguien realmente querría usarlo.
Caracol mecánico

1
@supercat: las referencias fantasmas (para el caso todas las referencias) se introdujeron en Java 2
Steve Jessop

1
@supercat: lo siento, sí, por "referencias" quise decir java.lang.ref.Referencey sus subclases. Java 1 tenía variables :-) Y sí, también tenía finalizadores.
Steve Jessop

2
@SteveJessop: si un constructor de clase base le pide a otras entidades que alteren su estado en nombre del objeto en construcción (un patrón muy común), pero un inicializador de campo de clase derivada arroja una excepción, el objeto parcialmente construido debe limpiarse inmediatamente después de sí mismo (es decir, notificar a las entidades externas que sus servicios ya no son necesarios), pero ni Java ni .NET ofrecen ningún patrón de limpieza remota a través del cual eso pueda suceder. De hecho, parecen hacer todo lo posible para dificultar que una referencia a la cosa que necesita limpieza alcance el código de limpieza.
supercat

57

Una regla simple: nunca use finalizadores.

Solo el hecho de que un objeto tiene un finalizador (independientemente del código que ejecute) es suficiente para causar una sobrecarga considerable para la recolección de basura.

De un artículo de Brian Goetz:

Los objetos con finalizadores (aquellos que tienen un método no trivial finalize ()) tienen una sobrecarga significativa en comparación con los objetos sin finalizadores, y deben usarse con moderación. Los objetos finalizables son más lentos de asignar y más lentos de recolectar. En el momento de la asignación, la JVM debe registrar cualquier objeto finalizable con el recolector de basura, y (al menos en la implementación de la JVM de HotSpot) los objetos finalizables deben seguir una ruta de asignación más lenta que la mayoría de los otros objetos. Del mismo modo, los objetos finalizables también son más lentos de recolectar. Se necesitan al menos dos ciclos de recolección de basura (en el mejor de los casos) antes de que se pueda reclamar un objeto finalizable, y el recolector de basura tiene que hacer un trabajo adicional para invocar al finalizador. El resultado es más tiempo dedicado a la asignación y recolección de objetos y más presión sobre el recolector de basura, porque la memoria utilizada por los objetos finalizables inalcanzables se retiene por más tiempo. Combine eso con el hecho de que no se garantiza que los finalizadores se ejecuten en un período de tiempo predecible, o incluso en absoluto, y puede ver que hay relativamente pocas situaciones en las que la finalización es la herramienta adecuada para usar.


46

La única vez que utilicé finalizar en el código de producción fue implementar una verificación de que los recursos de un objeto dado se habían limpiado, y si no, registrar un mensaje muy vocal. En realidad no lo intentó y lo hizo por sí mismo, solo gritaba mucho si no se hacía correctamente. Resultó ser bastante útil.


34

He estado haciendo Java profesionalmente desde 1998, y nunca lo he implementado finalize(). Ni una sola vez.


¿Cómo destruyo el objeto de clase pública entonces? Está causando problemas en el ADF. Básicamente, se supone que debe volver a cargar la página (obteniendo datos nuevos de una API), pero está tomando el objeto antiguo y mostrando los resultados
InfantPro'Aravind '

2
@ InfantPro'Aravind 'llamando a finalizar no elimina el objeto. Es un método que implementa que la JVM puede elegir llamar si y cuando recolecta basura su objeto.
Paul Tomblin

@PaulTomblin, oh gracias por ese detalle. ¿Alguna sugerencia de actualizar una página en ADF?
InfantPro'Aravind '

@ InfantPro'Aravind 'No tengo idea, nunca usé ADF.
Paul Tomblin

28

La respuesta aceptada es buena, solo quería agregar que ahora hay una manera de tener la funcionalidad de finalizar sin usarla en absoluto.

Mire las clases de "Referencia". Referencia débil, Phantom Reference y Soft Reference.

Puede usarlos para mantener una referencia a todos sus objetos, pero esta referencia SOLO no detendrá el GC. Lo bueno de esto es que puede hacer que llame a un método cuando se eliminará, y se puede garantizar que se llame a este método .

En cuanto a finalizar: utilicé finalizar una vez para comprender qué objetos se estaban liberando. Puedes jugar algunos juegos divertidos con estadísticas, conteo de referencias y demás, pero fue solo para análisis, pero ten cuidado con un código como este (no solo en finalización, sino que es donde es más probable que lo veas):

public void finalize() {
  ref1 = null;
  ref2 = null;
  othercrap = null;
}

Es una señal de que alguien no sabía lo que estaban haciendo. "Limpiar" de esta manera prácticamente nunca es necesario. Cuando la clase es GC'd, esto se hace automáticamente.

Si encuentra un código como ese en una finalización, se garantiza que la persona que lo escribió estaba confundida.

Si está en otra parte, podría ser que el código es un parche válido para un modelo incorrecto (una clase permanece durante mucho tiempo y, por alguna razón, las cosas a las que hace referencia tuvieron que liberarse manualmente antes de que el objeto sea GC'd). En general, es porque alguien olvidó eliminar a un oyente o algo y no puede entender por qué su objeto no está siendo GC, por lo que simplemente borran las cosas a las que se refiere y se encogen de hombros y se van.

Nunca debe usarse para limpiar cosas "más rápido".


3
Creo que las referencias fantasmas son una mejor manera de realizar un seguimiento de los objetos que se están liberando.
Bill Michell

2
votación por proporcionar la mejor explicación de "referencia débil" que he escuchado.
sscarduzio

Lamento que me haya tomado tantos años leer esto, pero realmente es una buena adición a las respuestas.
Spencer Kormos

27

No estoy seguro de qué puedes hacer con esto, pero ...

itsadok@laptop ~/jdk1.6.0_02/src/
$ find . -name "*.java" | xargs grep "void finalize()" | wc -l
41

Así que supongo que el Sol encontró algunos casos en los que (piensan) debería usarse.


12
Supongo que también se trata de "¿El desarrollador de Sun siempre tendrá la razón"?
CodeReaper

13
Pero, ¿cuántos de esos usos solo están implementando o probando el soporte en finalizelugar de usarlo realmente?
Caracol mecánico

Yo uso Windows ¿Cómo harías lo mismo en Windows?
gparyani

2
@damryfbfnetsi: intente instalar ack ( beyondgrep.com ) a través de chocolatey.org/packages/ack . O utilice una función simple Buscar en archivos en $FAVORITE_IDE.
Brian Cline

21
class MyObject {
    Test main;

    public MyObject(Test t) {    
        main = t; 
    }

    protected void finalize() {
        main.ref = this; // let instance become reachable again
        System.out.println("This is finalize"); //test finalize run only once
    }
}

class Test {
    MyObject ref;

    public static void main(String[] args) {
        Test test = new Test();
        test.ref = new MyObject(test);
        test.ref = null; //MyObject become unreachable,finalize will be invoked
        System.gc(); 
        if (test.ref != null) System.out.println("MyObject still alive!");  
    }
}

====================================

resultado:

This is finalize

MyObject still alive!

=====================================

Por lo tanto, puede hacer que una instancia inalcanzable sea accesible en el método de finalización.


21
Por alguna razón, este código me hace pensar en películas de zombies. Usted GC su objeto y luego se pone de pie de nuevo ...
steveha

arriba hay buenos códigos. Me preguntaba cómo hacer que el objeto sea alcanzable nuevamente nuevamente en este momento.
lwpro2

15
no, arriba hay códigos malos. Es un ejemplo de por qué finalizar es malo.
Tony

Recuerde que invocar System.gc();no es una garantía de que el recolector de basura realmente se ejecutará. Es la total discreción del GC limpiar el montón (quizás todavía haya suficiente montón libre, tal vez no esté demasiado fragmentado, ...). Entonces, es solo una humilde petición del programador "por favor, ¿podrían escucharme y salir corriendo?" y no un comando "¡corre ahora como digo!". Perdón por usar palabras linguales, pero dice exactamente cómo funciona el GC de JVM.
Roland

11

finalize()Puede ser útil para detectar fugas de recursos. Si el recurso debe cerrarse pero no es así, escriba el hecho de que no se cerró en un archivo de registro y ciérrelo. De esa manera, elimina la fuga de recursos y se da una manera de saber que ha sucedido para que pueda solucionarlo.

He estado programando en Java desde 1.0 alpha 3 (1995) y todavía tengo que anular finalizar para cualquier cosa ...


6

No debe depender de finalize () para limpiar sus recursos por usted. finalize () no se ejecutará hasta que la clase se recolecte basura, si es así. Es mucho mejor liberar recursos explícitamente cuando haya terminado de usarlos.


5

Para resaltar un punto en las respuestas anteriores: los finalizadores se ejecutarán en el único hilo GC. He oído hablar de una importante demostración de Sun en la que los desarrolladores agregaron una pequeña suspensión a algunos finalizadores e intencionalmente pusieron de rodillas una demo 3D de otro modo elegante.

Es mejor evitarlo, con la posible excepción de los diagnósticos test-env.

Eckel's Thinking in Java tiene una buena sección sobre esto.


4

Hmmm, una vez lo usé para limpiar objetos que no estaban siendo devueltos a un grupo existente.

Se les pasaba mucho, por lo que era imposible saber cuándo podrían regresar con seguridad a la piscina. El problema fue que introdujo una gran penalización durante la recolección de basura que fue mucho mayor que cualquier ahorro al agrupar los objetos. Estuvo en producción durante aproximadamente un mes antes de que arrancara todo el grupo, hiciera todo dinámico y terminé.


4

Ten cuidado con lo que haces en a finalize(). Especialmente si lo está utilizando para cosas como llamar a close () para asegurarse de que se limpien los recursos. Nos encontramos con varias situaciones en las que teníamos bibliotecas JNI vinculadas al código java en ejecución, y en cualquier circunstancia en la que utilizáramos finalize () para invocar métodos JNI, obtendríamos una corrupción de montón de Java muy mala. La corrupción no fue causada por el código JNI subyacente en sí, todos los rastros de memoria estaban bien en las bibliotecas nativas. Era solo el hecho de que estábamos llamando a los métodos JNI desde finalize ().

Esto fue con un JDK 1.5 que todavía está en uso generalizado.

No descubrimos que algo salió mal hasta mucho más tarde, pero al final el culpable siempre fue el método finalize () haciendo uso de llamadas JNI.


3

Al escribir código que será utilizado por otros desarrolladores que requiera algún tipo de método de "limpieza" para liberar recursos. A veces, esos otros desarrolladores se olvidan de llamar a su método de limpieza (o cierre, o destrucción, o lo que sea). Para evitar posibles fugas de recursos, puede verificar el método de finalización para asegurarse de que se llamó al método y, si no fue así, puede llamarlo usted mismo.

Muchos controladores de bases de datos hacen esto en sus implementaciones de Declaración y Conexión para proporcionar un poco de seguridad contra los desarrolladores que olvidan llamarlos de cerca.


2

Editar: Ok, realmente no funciona. Lo implementé y pensé que si falla a veces, eso está bien para mí, pero ni siquiera llamó al método finalize una sola vez.

No soy un programador profesional, pero en mi programa tengo un caso que creo que es un buen ejemplo de uso de finalize (), que es un caché que escribe su contenido en el disco antes de que se destruya. Como no es necesario que se ejecute cada vez que se destruye, solo acelera mi programa, espero que no lo haya hecho mal.

@Override
public void finalize()
{
    try {saveCache();} catch (Exception e)  {e.printStackTrace();}
}

public void saveCache() throws FileNotFoundException, IOException
{
    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("temp/cache.tmp"));
    out.writeObject(cache);
}

Creo que un caché (del tipo que tirarías al disco) es un buen ejemplo. E incluso si no se llama finalizar, no hay problema.
foo

2

Puede ser útil eliminar cosas que se han agregado a un lugar global / estático (por necesidad), y deben eliminarse cuando se elimina el objeto. Por ejemplo:

    privado vacío addGlobalClickListener () {
        weakAwtEventListener = new WeakAWTEventListener (esto);

        Toolkit.getDefaultToolkit (). AddAWTEventListener (weakAwtEventListener, AWTEvent.MOUSE_EVENT_MASK);
    }

    @Anular
    el vacío protegido finalize () arroja Throwable {
        super.finalize ();

        if (weakAwtEventListener! = null) {
            Toolkit.getDefaultToolkit (). RemoveAWTEventListener (weakAwtEventListener);
        }
    }

Esto requiere esfuerzos adicionales, ya que cuando el oyente tiene una fuerte referencia a la thisinstancia que se le pasó en el constructor, esa instancia nunca será inalcanzable, por lo tanto finalize(), nunca se limpiará de todos modos. Si, por otro lado, el oyente tiene una referencia débil a ese objeto, como su nombre indica, esta construcción corre el riesgo de limpiarse en momentos en que no debería. Los ciclos de vida de los objetos no son la herramienta adecuada para implementar la semántica de la aplicación.
Holger

0

iirc: puede usar el método de finalización como un medio para implementar un mecanismo de agrupación de recursos caros, para que no obtengan GC también.


0

Como nota al margen:

Un objeto que anula finalize () es tratado especialmente por el recolector de basura. Por lo general, un objeto se destruye inmediatamente durante el ciclo de recolección después de que el objeto ya no está dentro del alcance. Sin embargo, los objetos finalizables se mueven a una cola, donde los subprocesos de finalización separados agotarán la cola y ejecutarán el método finalize () en cada objeto. Una vez que finaliza el método finalize (), el objeto estará finalmente listo para la recolección de basura en el próximo ciclo.

Fuente: finalize () en desuso en java-9


0

Los recursos (Archivo, Socket, Stream, etc.) deben cerrarse una vez que hayamos terminado con ellos. Generalmente tienen un close()método que generalmente llamamos en la finallysección de try-catchdeclaraciones. Algunas vecesfinalize() también pueden ser utilizados por pocos desarrolladores, pero la OMI no es una forma adecuada, ya que no hay garantía de que finalice siempre.

En Java 7 tenemos una declaración de prueba con recursos que se puede usar como:

try (BufferedReader br = new BufferedReader(new FileReader(path))) {
  // Processing and other logic here.
} catch (Exception e) {
  // log exception
} finally {
  // Just in case we need to do some stuff here.
}

En el ejemplo anterior, try-with-resource cerrará automáticamente el recurso BufferedReaderinvocando el close()método. Si queremos, también podemos implementar Closeable en nuestras propias clases y usarlo de manera similar. En mi opinión, parece más ordenado y simple de entender.


0

Personalmente, casi nunca lo uso, finalize()excepto en una circunstancia rara: hice una colección personalizada de tipo genérico y escribí un finalize()método personalizado que hace lo siguiente:

public void finalize() throws Throwable {
    super.finalize();
    if (destructiveFinalize) {
        T item;
        for (int i = 0, l = length(); i < l; i++) {
            item = get(i);
            if (item == null) {
                continue;
            }
            if (item instanceof Window) {
                ((Window) get(i)).dispose();
            }
            if (item instanceof CompleteObject) {
                ((CompleteObject) get(i)).finalize();
            }
            set(i, null);
        }
    }
}

( CompleteObjectEs una interfaz que hice que le permite especificar que ha implementado implementadas raramente Objectmétodos como #finalize(), #hashCode()y #clone())

Entonces, usando un #setDestructivelyFinalizes(boolean)método hermano , el programa que usa mi colección puede (ayudar) a garantizar que la destrucción de una referencia a esta colección también destruya las referencias a su contenido y elimine cualquier ventana que pueda mantener viva la JVM sin querer. También consideré detener cualquier subproceso, pero eso abrió una nueva lata de gusanos.


44
Llamar finalize()a sus CompleteObjectinstancias como lo hace en el código anterior implica que podría llamarse dos veces, ya que la JVM aún podría invocar finalize()una vez que estos objetos se vuelvan inalcanzables, lo que, debido a la lógica de finalización, podría estar antes del finalize()método de su colección especial se llama o incluso simultáneamente al mismo tiempo. Como no veo ninguna medida para garantizar la seguridad de los hilos en su método, parece que no se da cuenta de esto ...
Holger

0

La respuesta aceptada enumera que se puede cerrar un recurso durante la finalización.

Sin embargo esta respuesta muestra que al menos en java8 con el compilador JIT, se encuentra con problemas inesperados en los que a veces se llama al finalizador incluso antes de terminar de leer una secuencia mantenida por su objeto.

Por lo tanto, incluso en esa situación, no se recomendaría finalizar .


Funcionaría, si la operación se hace segura para subprocesos, lo cual es imprescindible, ya que los finalizadores pueden ser llamados por subprocesos arbitrarios. Dado que la seguridad de subprocesos implementada correctamente implica que no solo la operación de cierre, sino también las operaciones que usan el recurso deben sincronizarse en el mismo objeto o bloqueo, habrá una relación antes de que ocurra entre el uso y la operación de cierre, que también previene demasiado pronto finalización Pero, por supuesto, a un alto precio por todo el uso ...
Holger
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.