¿Por qué se incluye el método de finalización en Java?

44

De acuerdo con esta publicación , nunca debemos confiar en el método de finalización que se llamará. Entonces, ¿por qué Java lo incluyó en el lenguaje de programación?

Parece una decisión terrible incluir en cualquier lenguaje de programación una función que pueda llamarse.

Patstuart
fuente

Respuestas:

41

Según el Java efectivo de Joshua Bloch (segunda edición), hay dos escenarios cuando finalize()es útil:

  1. Una es actuar como una "red de seguridad" en caso de que el propietario de un objeto se olvide de llamar a su método de terminación explícito. Si bien no hay garantía de que se invoque al finalizador de inmediato, puede ser mejor liberar el recurso tarde que nunca, en aquellos casos (con suerte raros) cuando el cliente no llama al método de terminación explícito. Pero el finalizador debe registrar una advertencia si encuentra que el recurso no se ha terminado

  2. Un segundo uso legítimo de finalizadores se refiere a objetos con pares nativos. Un par nativo es un objeto nativo al que un objeto normal delega a través de métodos nativos. Debido a que un par nativo no es un objeto normal, el recolector de basura no lo sabe y no puede reclamarlo cuando se reclama su par Java. Un finalizador es un vehículo apropiado para realizar esta tarea, suponiendo que el par nativo no tenga recursos críticos. Si el par nativo tiene recursos que deben terminarse rápidamente, la clase debe tener un método de terminación explícito, como se describió anteriormente. El método de terminación debe hacer lo que sea necesario para liberar el recurso crítico.

Para más información, consulte el Artículo 7, página 27.

bbalchev
fuente
44
Con # 2 suena como el igual nativo es un recurso nativo que requiere una red de seguridad y debe tener un método de terminación, y por lo tanto no debería ser un punto separado.
Mooing Duck
2
@MooingDuck: # 2 es un punto separado porque, si el par nativo no posee recursos críticos (por ejemplo, es puramente un objeto en memoria), entonces no hay necesidad de exponer un método de terminación explícito. La red de seguridad del # 1 trata explícitamente de tener un método de terminación.
jhominal
55

Los finalizadores son importantes para la gestión de los recursos nativos. Por ejemplo, su objeto podría necesitar asignar un WidgetHandle desde el sistema operativo usando una API que no sea Java. Si no liberas ese WidgetHandle cuando tu objeto es GC'd, vas a perder WidgetHandles.

Lo importante es que los casos de "finalizador nunca se llaman" se descomponen simplemente:

  1. El programa se cierra rápidamente.
  2. El objeto "vive para siempre" durante la vida del programa.
  3. La computadora se apaga / su proceso es eliminado por el sistema operativo / etc.

En los tres casos, no tiene una fuga nativa (en virtud del hecho de que su programa ya no se está ejecutando) o ya tiene una fuga no nativa (si sigue asignando objetos administrados sin que sean GC'd).

La advertencia "no confíe en que se llame al finalizador" se trata realmente de no usar finalizadores para la lógica del programa. Por ejemplo, no desea realizar un seguimiento de cuántos de sus objetos existen en todas las instancias de su programa incrementando un contador en un archivo en algún lugar durante la construcción y decrementándolo en un finalizador, porque no hay garantía de que sus objetos finalizado, este contador de archivos probablemente nunca volverá a 0. Este es realmente un caso especial del principio más general de que no debe depender de que su programa termine normalmente (fallas de energía, etc.).

Sin embargo, para la administración de recursos nativos, los casos en los que el finalizador no se ejecuta corresponden a casos en los que no le importa si no se ejecuta.

Ryan Cavanaugh
fuente
Excelente respuesta Pensé que incluso si pudiera llamarse ... aún debería incluirse. Estuve confundido por un tiempo con la pregunta y la vinculada en StackOverflow.
InformadoA
3
No estaba en la pregunta, pero vale la pena discutirlo en el contexto de "no confíe en que se llame al finalizador": se puede llamar al finalizador, pero solo después de un retraso arbitrariamente prolongado después de que el objeto se vuelva inalcanzable. Esto es importante para los "recursos nativos" que son mucho más escasos que la memoria (resulta que la mayoría de ellos).
26
"Los finalizadores son importantes para la gestión de los recursos nativos". - nooooooooo, ejem lo siento. Usar finalizadores para administrar recursos nativos es una idea horrible (por eso tenemos autocloseable y co ahora). El GC solo se preocupa por la memoria, no por ningún otro recurso. Esto significa que puede quedarse sin recursos nativos pero aún tener suficiente memoria para no activar un GC; realmente no queremos eso. Además, los finalizadores hacen que GC sea más caro, lo que tampoco es una buena idea.
Voo
12
Estoy de acuerdo en que aún debe tener una estrategia de gestión de los recursos nativos que no sea explícita finalizadores, pero si su objeto hace asignarlos y no los libera en el finalizador, usted es abrirse a una fuga nativo en el caso en que el objeto es GC'd sin la liberación explícita del recurso. En otras palabras, los finalizadores son una parte importante de una estrategia de gestión de recursos nativos, no una solución al problema en general.
Ryan Cavanaugh
1
@Voo es probable que sea más correcto decir el finalizador es la alternativa en caso de no seguirse el contrato del objeto ( close()no se llama antes de que sea inalcanzable)
trinquete monstruo
5

El propósito de este método se explica en la documentación de la API de la siguiente manera:

se invoca si y cuando la máquina virtual Java ha determinado que ya no hay ningún medio por el cual se pueda acceder a este objeto por ningún hilo que aún no haya muerto, excepto como resultado de una acción tomada por la finalización de algún otro objeto o clase que está lista para ser finalizada ...

El propósito habitual de finalize... es realizar acciones de limpieza antes de que el objeto se descarte irrevocablemente . Por ejemplo, el método de finalización para un objeto que representa una conexión de entrada / salida podría realizar transacciones de E / S explícitas para romper la conexión antes de que el objeto se descarte permanentemente ...


Si también está interesado en las razones por las cuales los diseñadores de idiomas han elegido que "el objeto se descarta irrevocablemente" ( basura recolectada ) de la manera que está más allá del control del programador de aplicaciones ("nunca debemos confiar"), esto se ha explicado en una respuesta a pregunta :

recolección automática de basura ... elimina clases enteras de errores de programación que perjudican a los programadores C y C ++. Puede desarrollar código Java con la confianza de que el sistema encontrará muchos errores rápidamente y que los principales problemas no permanecerán inactivos hasta que se haya enviado su código de producción.

La cita anterior, a su vez, se tomó de la documentación oficial sobre los objetivos de diseño de Java , es decir, se puede considerar una referencia autorizada que explica por qué los diseñadores de lenguaje Java decidieron de esta manera.

Para una discusión más detallada y agnóstica del lenguaje de esta preferencia, consulte la sección 9.6 de OOSC Gestión automática de memoria (en realidad, no solo vale la pena leer esta sección, sino todo el capítulo 9 si está interesado en cosas como esa). Esta sección se abre con una declaración inequívoca:

Un buen entorno OO debería ofrecer un mecanismo automático de administración de memoria que detecte y recupere objetos inalcanzables, permitiendo a los desarrolladores de aplicaciones concentrarse en su trabajo: el desarrollo de aplicaciones.

La discusión anterior debería ser suficiente para mostrar cuán importante es tener una instalación de este tipo disponible. En palabras de Michael Schweitzer y Lambert Strether:

Un programa orientado a objetos sin administración automática de memoria es más o menos lo mismo que una olla a presión sin una válvula de seguridad: ¡tarde o temprano la cosa seguramente explotará!

mosquito
fuente
4

Los finalizadores existen porque se esperaba que fueran un medio eficaz para garantizar que las cosas se limpien (aunque en la práctica no lo son), y porque cuando se inventaron, mejores medios para garantizar la limpieza (como referencias fantasmas y pruebas) -resources) aún no existía. En retrospectiva, Java probablemente sería mejor si el esfuerzo dedicado a implementar su instalación de "finalización" se hubiera dedicado a otros medios de limpieza, pero eso no estuvo claro durante el tiempo en que Java se desarrolló inicialmente.

Super gato
fuente
3

CAVEAT: Puede que esté desactualizado, pero así lo entiendo hace unos años:

En general, no hay garantía de cuándo se ejecuta un finalizador, o incluso si se ejecuta en absoluto, aunque algunas JVM le permitirán solicitar un GC completo y una finalización antes de que el programa salga (lo que, por supuesto, significa que el programa lleva más tiempo). para salir, y que no es el modo de operación predeterminado).

Y se sabía que algunos GC retrasaban o evitaban explícitamente los objetos de GC que tenían finalizadores, con la esperanza de que esto produjera un mejor rendimiento en los puntos de referencia.

Desafortunadamente, estos comportamientos entran en conflicto con las razones originales por las que se recomendaron los finalizadores, y en su lugar han alentado el uso de métodos de apagado explícitamente llamados.

Si tiene un objeto que realmente tiene que limpiarse antes de ser descartado, y si realmente no puede confiar en que los usuarios lo hagan, puede valer la pena considerar un finalizador. Pero en general, hay buenas razones por las que no las ve tan a menudo en el código Java moderno como lo hizo en algunos de los primeros ejemplos.

keshlam
fuente
1

La Guía de estilo de Java de Google tiene algunos sabios consejos sobre el tema:

Es extremadamente raro anular Object.finalize.

Consejo : no lo hagas. Si es absolutamente necesario, primero lea y entienda el Elemento 7 de Java Efectivo , "Evite Finalizadores", con mucho cuidado, y luego no lo haga.

Daniel Pryden
fuente
55
Por qué admiro la simplificación excesiva del problema, "no lo hagas" no es una respuesta a "por qué existe".
Pierre Arlaud
1
Supongo que no lo deletreé bien en mi respuesta, pero (IMO) la respuesta a "¿por qué existe?" es "no debería". Para esos casos raros en los que realmente necesita una operación de finalización, probablemente desee construirla usted mismo PhantomReferencey en su ReferenceQueuelugar.
Daniel Pryden
Quise decir mientras * obviamente, aunque no te menosprecié. Sin embargo, la respuesta más votada muestra cuán útil es el método, invalidando un poco su punto.
Pierre Arlaud
1
Incluso en el caso de pares nativos, creo que PhantomReferencees una mejor solución. Los finalizadores son una verruga que quedó de los primeros días de Java, y los me gusta Object.clone()y los tipos sin formato son parte del lenguaje que mejor se olvida.
Daniel Pryden
1
Los peligros de los finalizadores se describen de manera más accesible (comprensible para los desarrolladores) en C #. Sin embargo, uno no debe implicar que los mecanismos subyacentes en Java y C # son los mismos; No hay nada en sus especificaciones que lo diga.
rwong
1

La especificación del lenguaje Java (Java SE 7) establece:

Los finalizadores brindan la oportunidad de liberar recursos que un administrador de almacenamiento automático no puede liberar automáticamente. En tales situaciones, simplemente reclamar la memoria utilizada por un objeto no garantizaría que los recursos que poseía serían reclamados.

Benni
fuente