jdk - java virtual machine descargar
¿Por qué implementarías finalize()? (21)
He estado leyendo muchas de las preguntas de novato de Java en finalize () y me parece algo desconcertante que nadie haya dejado claro que finalize () es una forma poco confiable de limpiar recursos. Vi a alguien comentar que lo usan para limpiar Conexiones, lo cual es realmente aterrador, ya que la única manera de acercarse lo más posible a una garantía de que una Conexión está cerrada es implementar el intento (captura) finalmente.
No estudié en CS, pero llevo casi una década programando en Java profesionalmente y nunca he visto a nadie implementar Finalize () en un sistema de producción. Esto todavía no significa que no tenga sus usos, o que las personas con las que he trabajado lo hayan estado haciendo bien.
Entonces, mi pregunta es, ¿qué casos de uso existen para implementar finalize () que no se puedan manejar de manera más confiable a través de otro proceso o sintaxis dentro del lenguaje?
Por favor proporcione escenarios específicos o su experiencia, simplemente repetir un libro de texto Java o finalizar el uso previsto no es suficiente, y no es la intención de esta pregunta.
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á en el alcance. Sin embargo, los objetos finalizables se mueven a una cola, donde subprocesos de finalización separados drenarán la cola y ejecutarán el método finalize () en cada objeto. Una vez que finalice el método finalize (), el objeto estará listo para la recolección de basura en el próximo ciclo.
Cuando se escribe un código que será usado por otros desarrolladores que requieren algún tipo de método de "limpieza" para ser llamado para liberar recursos. A veces esos otros desarrolladores olvidan llamar a su método de limpieza (o cierre, 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 haya llamado al método y, de lo contrario, puede hacerlo usted mismo.
Muchos controladores de base de datos hacen esto en sus implementaciones de Declaración y Conexión para proporcionar un poco de seguridad contra los desarrolladores que se olvidan de cerrarlos.
Edit: está bien, realmente no funciona. Lo implementé y pensé que si falla, a veces eso está bien para mí, pero ni siquiera llamó al método de finalización una sola vez.
No soy un programador profesional, pero en mi programa tengo un caso que creo que es un ejemplo de un buen caso de usar 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);
}
Finalizar puede ser útil para detectar las fugas de recursos. Si el recurso debe cerrarse pero no se escribe, 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 ocurrido para que pueda solucionarlo.
He estado programando en Java desde 1.0 alpha 3 (1995) y todavía tengo que anular la finalización de cualquier cosa ...
He estado haciendo Java profesionalmente desde 1998, y nunca he implementado finalize (). Ni una sola vez.
Hmmm, una vez lo usé para limpiar objetos que no estaban siendo devueltos a un grupo existente. Se pasaron mucho por ahí, por lo que era imposible saber cuándo se podían devolver a la piscina de manera segura. 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, hice que todo fuera dinámico y se hizo con eso.
La única vez que utilicé Finalize en el código de producción fue para implementar una verificación de que se habían limpiado los recursos de un determinado objeto y, de no ser así, registrar un mensaje muy vocal. En realidad no intentó hacerlo, solo gritó mucho si no se hizo correctamente. Resultó ser bastante útil.
Los recursos (Archivo, Socket, Secuencia, etc.) deben cerrarse una vez que hayamos terminado con su uso. Por lo general, tienen close()
método close()
que generalmente llamamos en la sección close()
try-catch
declaraciones try-catch
. A veces, algunos desarrolladores también pueden utilizar finalize()
pero IMO no es una forma adecuada, ya que no hay garantía de que se llamará finalize siempre.
En Java 7 tenemos una declaración try-with-resources 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 BufferedReader
invocando el método close()
. Si queremos también podemos implementar Closeable en nuestras propias clases y usarlo de manera similar. En mi opinión, parece más limpio y fácil de entender.
No debe depender de finalize () para limpiar sus recursos para usted. finalize () no se ejecutará hasta que la clase sea recogida de basura, si es así. Es mucho mejor liberar recursos explícitamente cuando hayas terminado de usarlos.
No estoy seguro de qué puedes hacer con esto, pero ...
public void finalize() {
ref1 = null;
ref2 = null;
othercrap = null;
}
Así que supongo que el Sol encontró algunos casos en los que (ellos piensan) debería usarse.
Para resaltar un punto en las respuestas anteriores: los finalizadores se ejecutarán en el subproceso del GC solitario. He oído hablar de una gran demostración de Sun en la que los desarrolladores agregaron un poco de sueño a algunos finalizadores e intencionalmente pusieron de rodillas una demo en 3D de lo contrario elegante.
Lo mejor es evitar, con la posible excepción de los diagnósticos de prueba-env.
El pensamiento de Eckel en Java tiene una buena sección sobre esto.
Personalmente, casi nunca uso finalize()
excepto en una circunstancia rara: hice una colección personalizada de tipo genérico y escribí un método personalizado finalize()
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);
}
}
}
( CompleteObject
es una interfaz que hice que te permite especificar que has implementado métodos de Object
raramente implementados como #finalize()
, #hashCode()
y #clone()
)
Por lo tanto, al usar el #setDestructivelyFinalizes(boolean)
hermano #setDestructivelyFinalizes(boolean)
, el programa que usa mi colección puede (ayudar) a garantizar que la destrucción de una referencia a esta colección también destruye las referencias a su contenido y elimina cualquier ventana que pueda mantener la JVM sin querer. Consideré también detener cualquier hilo, pero eso abrió una lata de gusanos completamente nueva.
Podría usarlo como respaldo para un objeto que contenga un recurso externo (socket, archivo, etc.). Implementar un método close()
y el documento que se debe llamar.
Implemente finalize()
para realizar el proceso de close()
si detecta que no se ha realizado. Tal vez con algo tirado a Stderr para señalar que está limpiando después de una llamada de buggy.
Proporciona seguridad extra en una situación excepcional / buggy. No todas las personas que llaman van a hacer el try {} finally {}
correcto try {} finally {}
todo el tiempo. Desafortunado, pero cierto en la mayoría de los entornos.
Estoy de acuerdo en que rara vez es necesario. Y como señalan los comentaristas, viene con gastos generales de GC. Solo úselo 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 apuntan a java.lang.ref.Cleaner
y java.lang.ref.PhantomReference
como alternativas.
Puede ser útil eliminar cosas que se han agregado a un lugar global / estático (fuera de necesidad), y deben eliminarse cuando se elimina el objeto. Por ejemplo:
private void addGlobalClickListener() { weakAwtEventListener = new WeakAWTEventListener(this); Toolkit.getDefaultToolkit().addAWTEventListener(weakAwtEventListener, AWTEvent.MOUSE_EVENT_MASK); } @Override protected void finalize() throws Throwable { super.finalize(); if(weakAwtEventListener != null) { Toolkit.getDefaultToolkit().removeAWTEventListener(weakAwtEventListener); } }
Se pueden hacer las listas de respuestas aceptadas que cierran un recurso durante la finalización.
Sin embargo, esta respuesta muestra que al menos en java8 con el compilador JIT, te encuentras con problemas inesperados donde a veces se llama al finalizador incluso antes de terminar de leer una secuencia mantenida por tu objeto.
Así que incluso en esa situación no sería recomendable llamar a finalización.
Ten cuidado con lo que haces en una finalización (). Especialmente si lo está utilizando para cosas como llamar a close () para asegurarse de que se limpian los recursos. Nos topamos con varias situaciones en las que teníamos bibliotecas JNI vinculadas al código Java en ejecución, y en cualquier circunstancia en las que utilizamos finalize () para invocar métodos JNI, obtendríamos una corrupción muy grave en el montón de Java. La corrupción no fue causada por el código JNI subyacente, todas las trazas de memoria estaban bien en las bibliotecas nativas. Era solo el hecho de que estábamos llamando métodos JNI desde el finalize () en absoluto.
Esto fue con un JDK 1.5 que todavía está en uso generalizado.
No descubriríamos que algo salió mal hasta mucho más tarde, pero al final el culpable fue siempre el método finalize () utilizando las llamadas JNI.
Una regla simple: nunca use finalizadores. El solo hecho de que un objeto tenga un finalizador (independientemente del código que ejecute) es suficiente para generar una sobrecarga considerable para la recolección de basura.
De un article de Brian Goetz:
Los objetos con finalizadores (aquellos que tienen un método no finalista () no trivial) 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 HotSpot JVM) los objetos finalizables deben seguir una ruta de asignación más lenta que la mayoría de los otros objetos. De manera similar, los objetos finalizables también son más lentos de recolectar. Se requieren al menos dos ciclos de recolección de basura (en el mejor de los casos) antes de poder reclamar un objeto finalizable, y el recolector de basura tiene que hacer un trabajo adicional para invocar el finalizador. El resultado es más tiempo dedicado a la asignación y recopilación de objetos y más presión en 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 para las cuales la finalización es la herramienta adecuada para usar.
Usé Finalize una vez para entender qué objetos estaban siendo liberados. Puedes jugar algunos juegos ingeniosos con estadísticas, conteo de referencias y demás, pero fue solo para análisis.
La respuesta aceptada es buena, solo quería agregar que ahora hay una manera de tener la funcionalidad de finalizar sin usarla en absoluto.
Mira las clases de "Referencia". Referencia débil, etc.
Puede usarlos para mantener una referencia a todos sus objetos, pero esta referencia ALONE no detendrá GC. Lo mejor 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.
Otra cosa a tener en cuenta. En cualquier momento que vea algo como esto en cualquier lugar en el código (no solo para finalizar, sino que es donde es más probable que lo vea):
itsadok@laptop ~/jdk1.6.0_02/src/
$ find . -name "*.java" | xargs grep "void finalize()" | wc -l
41
Es una señal de que alguien no sabía lo que estaban haciendo. "Limpiar" como esto virtualmente 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 sea un parche válido para un modelo incorrecto (una clase permanece durante mucho tiempo y, por alguna razón, las cosas a las que hacía referencia debían liberarse manualmente antes de que el objeto fuera GC''d). En general, es porque alguien se olvidó de eliminar a un oyente o algo y no puede entender por qué su objeto no está siendo golpeado, así que simplemente eliminan las cosas a las que se refiere, se encogen de hombros y se van.
Nunca debe utilizarse para limpiar las cosas "más rápido".
finalize () es una sugerencia para la JVM de que podría ser bueno ejecutar su código en un momento no especificado. Esto es bueno cuando quieres que el código falle misteriosamente.
Hacer algo significativo en los finalizadores (básicamente cualquier cosa excepto el registro) también es bueno en tres situaciones:
- quiere apostar a que otros objetos finalizados todavía estará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 los objetos finalizados y pasar mucho tiempo tratando de averiguar por qué no funcionan y / o por qué no se finalizan cuando finalmente se liberan.
Si cree que necesita finalize (), a veces lo que realmente desea es una referencia fantasma (que en el ejemplo dado podría contener una referencia firme a una conexión utilizada por su referand, y cerrarla después de que la referencia fantasma haya sido puesta en cola). Esto también tiene la propiedad de que puede que nunca se ejecute misteriosamente, pero al menos no puede invocar métodos ni resucitar objetos finalizados. Así que es justo para situaciones en las que no es absolutamente necesario cerrar esa conexión de manera limpia, pero le gustaría hacerlo, y los clientes de su clase no pueden o no quieren cerrar la llamada por sí mismos (lo que en realidad es lo suficientemente justo) ¿Cuál es el punto de tener un recolector de basura en absoluto si diseña interfaces que requieren que se realice una acción específica antes de la recolección? Eso nos pone de nuevo en los días de malloc / free.)
Otras veces necesitas el recurso que crees que estás logrando para ser más robusto. Por ejemplo, ¿por qué necesitas cerrar esa conexión? En última instancia, debe basarse en algún tipo de E / S provista por el sistema (socket, archivo, lo que sea), ¿por qué no puede confiar en que el sistema lo cierre por usted cuando se cuente con el nivel más bajo de recursos? Si el servidor en el otro extremo requiere absolutamente que cierre la conexión limpiamente en lugar de simplemente desconectar el zócalo, ¿qué sucederá cuando alguien se tropiece con el cable de alimentación de la máquina en la que se ejecuta su código o se apaga la red intermedia?
Descargo de responsabilidad: he trabajado en una implementación de JVM en el pasado. Odio los finalizadores.
iirc: puede utilizar el método de finalización como un medio para implementar un mecanismo de agrupación de recursos costosos, de modo que no obtengan GC también.
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:
Esto es finalizar
¡MyObject sigue vivo!
=====================================
Por lo tanto, puede hacer que una instancia inalcanzable sea accesible en el método de finalización.