Los dos argumentos principales contra la anulación Object.finalize()
son:
No puedes decidir cuándo se llama.
Es posible que no se llame en absoluto.
Si entiendo esto correctamente, no creo que sean razones suficientes para odiar Object.finalize()
tanto.
Depende de la implementación de la VM y del GC determinar cuándo es el momento adecuado para desasignar un objeto, no el desarrollador. ¿Por qué es importante decidir cuándo
Object.finalize()
se llama?Normalmente, y corríjame si me equivoco, el único momento en
Object.finalize()
que no se llamaría es cuando la aplicación se terminara antes de que el GC tuviera la oportunidad de ejecutarse. Sin embargo, el objeto se desasignó de todos modos cuando el proceso de la aplicación terminó con él. EntoncesObject.finalize()
no me llamaron porque no era necesario llamarlo. ¿Por qué le importaría al desarrollador?
Cada vez que uso objetos que tengo que cerrar manualmente (como los identificadores de archivos y las conexiones), me siento muy frustrado. Tengo que estar constantemente comprobando si un objeto tiene una implementación close()
, y estoy seguro de que he perdido algunas llamadas en algunos puntos en el pasado. ¿Por qué no es más simple y seguro dejar que la VM y el GC eliminen estos objetos mediante la close()
implementación Object.finalize()
?
fuente
finalize()
está un poco desordenada. Si alguna vez lo implementa, asegúrese de que sea seguro para subprocesos con respecto a todos los demás métodos en el mismo objeto.Respuestas:
En mi experiencia, hay una y solo una razón para anular
Object.finalize()
, pero es una muy buena razón :El análisis estático solo puede detectar omisiones en escenarios de uso triviales, y las advertencias del compilador mencionadas en otra respuesta tienen una visión tan simplista de las cosas que realmente tiene que deshabilitarlas para hacer algo no trivial. (Tengo muchas más advertencias habilitadas que cualquier otro programador que conozca o haya escuchado, pero no tengo habilitadas las advertencias estúpidas).
La finalización puede parecer un buen mecanismo para asegurarse de que los recursos no se disuelvan, pero la mayoría de las personas lo ven de una manera completamente incorrecta: piensan que es un mecanismo alternativo alternativo, una salvaguardia de "segunda oportunidad" que salvará automáticamente día al deshacerse de los recursos que olvidaron. Esto está muy mal . Debe haber una sola forma de hacer cualquier cosa: o siempre cierras todo o la finalización siempre cierra todo. Pero como la finalización no es confiable, la finalización no puede serlo.
Por lo tanto, existe este esquema que llamo Eliminación obligatoria , y estipula que el programador es responsable de cerrar siempre explícitamente todo lo que implementa
Closeable
oAutoCloseable
. (La declaración de prueba con recursos todavía cuenta como cierre explícito). Por supuesto, el programador puede olvidar, así que ahí es donde entra en juego la finalización, pero no como un hada mágica que mágicamente hará las cosas bien al final: si la finalización descubre queclose()
no fue invocado, nointente invocarlo, precisamente porque (con certeza matemática) habrá hordas de programadores n00b que dependerán de él para hacer el trabajo que eran demasiado vagos o demasiado distraídos para hacer. Entonces, con la disposición obligatoria, cuando la finalización descubre queclose()
no se invocó, registra un mensaje de error rojo brillante, diciéndole al programador con mayúsculas grandes y gruesas que arreglen sus cosas.Como beneficio adicional, se rumorea que "la JVM ignorará un método trivial finalize () (p. Ej., Uno que simplemente regresa sin hacer nada, como el definido en la clase Object)", por lo que con la eliminación obligatoria puede evitar toda finalización sobrecarga en todo su sistema ( vea la respuesta de alip para obtener información sobre cuán terrible es esta sobrecarga) codificando su
finalize()
método de esta manera:La idea detrás de esto es que
Global.DEBUG
es unastatic final
variable cuyo valor se conoce en el momento de la compilación, por lo que si esfalse
así, el compilador no emitirá ningún código para toda laif
declaración, lo que lo convertirá en un finalizador trivial (vacío), que a su vez significa que su clase será tratada como si no tuviera un finalizador. (En C # esto se haría con un buen#if DEBUG
bloque, pero qué podemos hacer, esto es Java, donde pagamos aparente simplicidad en el código con una sobrecarga adicional en el cerebro).Más información sobre la eliminación obligatoria, con una discusión adicional sobre la eliminación de recursos en dot Net, aquí: michael.gr: eliminación obligatoria frente a la abominación de "eliminación y eliminación"
fuente
close()
, por si acaso. Creo que si no se puede confiar en mis pruebas, será mejor que no lance el sistema a producción.if( Global.DEBUG && ...
construcción funcione de modo que la JVM ignore elfinalize()
método como trivial,Global.DEBUG
debe establecerse en el momento de la compilación (en lugar de inyectado, etc.), de modo que lo que sigue será un código muerto. ¡La llamada alsuper.finalize()
bloque if también es suficiente para que la JVM lo trate como no trivial (independientemente del valor deGlobal.DEBUG
, al menos en HotSpot 1.8), incluso si la superclase#finalize()
es trivial!java.io
. Si ese tipo de seguridad de hilo no estaba en la lista de deseos, se suma a la sobrecarga inducida porfinalize()
...Debido a que los identificadores y las conexiones de los archivos (es decir, los descriptores de archivos en los sistemas Linux y POSIX) son un recurso bastante escaso (es posible que esté limitado a 256 de ellos en algunos sistemas, o a 16384 en otros; ver setrlimit (2) ). No hay garantía de que se llame al GC con la suficiente frecuencia (o en el momento adecuado) para evitar agotar estos recursos limitados. Y si el GC no se llama lo suficiente (o la finalización no se ejecuta en el momento adecuado), alcanzará ese límite (posiblemente bajo).
La finalización es una cuestión de "mejor esfuerzo" en la JVM. Es posible que no se llame, o que se llame bastante tarde ... En particular, si tiene mucha RAM o si su programa no asigna muchos objetos (o si la mayoría de ellos mueren antes de ser enviados a algunos lo suficientemente mayores) generación por un GC generacional de copia), el GC podría llamarse muy raramente, y las finalizaciones podrían no ejecutarse muy a menudo (o incluso podrían no ejecutarse en absoluto).
Entonces
close
los descriptores de archivo explícitamente, si es posible. Si tiene miedo de filtrarlos, use la finalización como una medida adicional, no como la principal.fuente
Mire el asunto de esta manera: solo debe escribir código que sea (a) correcto (de lo contrario, su programa es completamente incorrecto) y (b) necesario (de lo contrario, su código es demasiado grande, lo que significa más RAM necesaria, más ciclos gastados en cosas inútiles, más esfuerzo para entenderlo, más tiempo dedicado a mantenerlo, etc., etc.)
Ahora considere qué es lo que quiere hacer en un finalizador. O es necesario. En ese caso, no puede colocarlo en un finalizador, porque no sabe si se llamará. Eso no es lo suficientemente bueno. O no es necesario, ¡entonces no deberías escribirlo en primer lugar! De cualquier manera, ponerlo en el finalizador es la elección incorrecta.
(Tenga en cuenta que los ejemplos que nombra, como cerrar secuencias de archivos, parecen no ser realmente necesarios, pero lo son. Es solo que hasta que llegue al límite de los identificadores de archivos abiertos en su sistema, no notará que su código es incorrecto. Pero ese límite es una característica del sistema operativo y por lo tanto es aún más impredecible que la política de la JVM para finalizadores, por lo que realmente es importante que usted no pierde identificadores de archivo.)
fuente
finalize()
para cerrarlo en caso de que un ciclo gc realmente necesite liberarse RAM. De lo contrario, mantendría el objeto en la RAM en lugar de tener que regenerarlo y cerrarlo cada vez que necesite usarlo. Claro, los recursos que abre no se liberarán hasta que el objeto sea GCed, siempre que sea posible, pero es posible que no necesite garantizar que mis recursos se liberen en un momento determinado.Una de las principales razones para no confiar en los finalizadores es que la mayoría de los recursos que uno podría estar tentado a limpiar en el finalizador son muy limitados. El recolector de basura solo se ejecuta de vez en cuando, ya que atravesar referencias para determinar si algo se puede liberar o no es costoso. Esto significa que podría pasar 'un tiempo' antes de que tus objetos se destruyan realmente. Si tiene muchos objetos que abren conexiones de bases de datos de corta duración, por ejemplo, dejar que los finalizadores limpien estas conexiones podría agotar su grupo de conexiones mientras espera que el recolector de basura finalmente se ejecute y libere las conexiones terminadas. Luego, debido a la espera, terminas con una gran cantidad de pedidos pendientes en cola, que agotan rápidamente el grupo de conexiones nuevamente. Eso'
Además, el uso de prueba con recursos hace que cerrar objetos 'cerrables' al finalizar sea fácil de hacer. Si no está familiarizado con esta construcción, le sugiero que la consulte: https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
fuente
Además de por qué dejar que el finalizador libere recursos es generalmente una mala idea, los objetos finalizables vienen con una sobrecarga de rendimiento.
De la teoría y práctica de Java: recolección y rendimiento de basura (Brian Goetz), los finalizadores no son tus amigos :
fuente
finalize()
.Mi (menos) razón favorita para evitar
Object.finalize
es que los objetos pueden finalizar después de que lo esperas, sino que pueden finalizarse antes de que puedas esperarlo. El problema no es que un objeto que todavía está dentro del alcance se pueda finalizar antes de salir del alcance si Java decide que ya no es accesible.Vea esta pregunta para más detalles. Aún más divertido es que esta decisión solo se puede tomar después de que la optimización del punto caliente se active, haciendo que sea doloroso depurarla.
fuente
HasFinalize.stream
debería ser un objeto finalizable por separado. Es decir, la finalización deHasFinalize
no debería finalizar o intentar limpiarstream
. O si debería, entonces debería hacerstream
inaccesible.En Eclipse, recibo una advertencia cada vez que olvido cerrar algo que implementa
Closeable
/AutoCloseable
. No estoy seguro de si eso es algo de Eclipse o si es parte del compilador oficial, pero podría considerar el uso de herramientas de análisis estático similares para ayudarlo allí. FindBugs, por ejemplo, probablemente pueda ayudarlo a verificar si ha olvidado cerrar un recurso.fuente
AutoCloseable
. Hace que sea muy fácil administrar recursos con la función de probar con recursos. Esto anula varios de los argumentos en la pregunta.A tu primera pregunta:
Bueno, la JVM determinará cuándo es un buen punto reclamar el almacenamiento que se ha asignado a un objeto. Este no es necesariamente el momento en que debe realizarse la limpieza del recurso, que desea realizar
finalize()
. Esto se ilustra en la pregunta "finalize () llamada a un objeto muy accesible en Java 8" sobre SO. Allí, unclose()
método ha sido invocado por unfinalize()
método, mientras que todavía está pendiente un intento de leer del flujo por el mismo objeto. Por lo tanto, además de la conocida posibilidad quefinalize()
se llama demasiado tarde, existe la posibilidad de que se llame demasiado temprano.La premisa de su segunda pregunta:
Simplemente está mal. No hay ningún requisito para que una JVM admita la finalización en absoluto. Bueno, no está completamente equivocado, ya que aún podría interpretarlo como "la aplicación se finalizó antes de que finalice la finalización", suponiendo que su aplicación alguna vez finalice.
Pero tenga en cuenta la pequeña diferencia entre "GC" de su declaración original y el término "finalización". La recolección de basura es distinta a la finalización. Una vez que la administración de memoria detecta que un objeto es inalcanzable, simplemente puede reclamar su espacio, si no tiene un
finalize()
método especial o la finalización simplemente no es compatible, o puede poner en cola el objeto para su finalización. Por lo tanto, la finalización de un ciclo de recolección de basura no implica que se ejecuten los finalizadores. Eso puede suceder más adelante, cuando se procesa la cola, o nunca.Este punto es también la razón por la cual incluso en JVM con soporte de finalización, confiar en él para la limpieza de recursos es peligroso. La recolección de basura es parte de la administración de memoria y, por lo tanto, se desencadena por las necesidades de memoria. Es posible que la recolección de basura nunca se ejecute porque hay suficiente memoria durante todo el tiempo de ejecución (bueno, eso todavía encaja en la descripción de "la aplicación se terminó antes de que el GC tenga la oportunidad de ejecutarse", de alguna manera). También es posible que el GC se ejecute, pero luego, hay suficiente memoria reclamada, por lo que la cola del finalizador no se procesa.
En otras palabras, los recursos nativos administrados de esta manera siguen siendo ajenos a la administración de la memoria. Si bien está garantizado que
OutOfMemoryError
solo se lanza después de suficientes intentos de liberar memoria, eso no se aplica a los recursos nativos y la finalización. Es posible que la apertura de un archivo falle debido a recursos insuficientes mientras la cola de finalización está llena de objetos que podrían liberar estos recursos, si el finalizador alguna vez se ejecutó ...fuente