Limpie los escuchas y los enlaces de propiedad JavaFX (fugas de memoria)

No he encontrado una respuesta simple para estas dos preguntas:

¿Tengo que eliminar una escucha antes de eliminar la instancia de propiedad (la escucha no se usa en ningún otro lugar)?

BooleanProperty bool = new SimpleBooleanProperty();
bool.addListener(myListener);
bool.removeListener(myListener); // is it necessary to do this?
bool = null;

¿Tengo que desvincular una propiedad enlazada unidireccional antes de eliminar la instancia de propiedad?

BooleanProperty bool = new SimpleBooleanProperty();
bool.bind(otherBool);
bool.unbind(); // is it necessary to do this?
bool = null;
 invariant28 ene. 2013 17:13
ver discusión similar aquí:forums.oracle.com/forums/…
 dpelisek29 ene. 2013 11:19
Invariante, ese fue el primer resultado cuando puse las palabras clave en google. Asegúrate de que no preguntaría si la respuesta estaba realmente allí :)

Respuestas a la pregunta(2)

Solución de preguntas
Caso 1

myListener "no se usa en ningún otro lugar" y, por lo tanto, asumo que, como variable local [method-], la respuesta esno. En el caso general, sin embargo, la respuesta es principalmente unano pero a veces puede ser unsí.

MientrasmyListener es muy accesible, entonces nunca será elegible para la finalización y seguirá consumiendo memoria. Por ejemplo, este sería el caso simyListener es un "normalmente" declaradostatic variable (* todas las referencias "normales" en Java sonfuerte referencias *). Sin embargo, simyListener es una variable local, entonces el objeto ya no será accesible después del retorno de la llamada al método actual ybool.removeListener(myListener) Es un poco sin sentido sobre la ingeniería. Tanto el observador como elObservable queda fuera de alcance y eventualmente será finalizado. Una cita de mi propiaentrada en el blog acerca de esta respuesta podría pintar una mejor imagen:

No importa si la caja sabe sobre el gato que está dentro, si la arrojas al océano. Si la caja no es accesible, tampoco lo es el gato.

Teoría

Para comprender completamente la situación aquí, debemos recordarnos el ciclo de vida de un objeto Java (fuente):

Un objeto es muy accesible si puede ser alcanzado por algún hilo sin atravesar ningún objeto de referencia. Un objeto recién creado es fuertemente accesible por el hilo que lo creó. [..] Un objeto es débilmente accesible si [no] es fuertemente [..] accesible pero se puede alcanzar al atravesar una referencia débil. Cuando se borran las referencias débiles a un objeto débilmente accesible, el objeto se vuelve elegible para su finalización.

En el caso de las variables estáticas, éstas siempre serán accesibles siempre que la clase esté cargada, de modo que sea accesible. Si no quisiéramos que una referencia estática fuera la que impedía que el recolector de basura hiciera su trabajo, entonces podríamos declarar la variable para usar unaWeakReference en lugar. JavaDoc dice:

Los objetos de referencia débiles [..] no impiden que sus referentes se conviertan en finalizables, finalizados y luego reclamados. [..] Supongamos que el recolector de basura determina en un determinado momento que un objeto es poco accesible. En ese momento, borrará atómicamente todas las referencias débiles a ese objeto [..]. Al mismo tiempo, declarará que todos los objetos que antes eran poco accesibles se pueden finalizar.

Gestión explícita

Para ilustrar, supongamos que escribimos un juego de simulación espacial JavaFX. Cada vez que unObservable El planeta se mueve a la vista de un observador de una nave espacial, el motor del juego registra la nave espacial con el planeta. Es bastante evidente que cada vez que el planeta se pierde de vista, el motor del juego también debería eliminar la nave espacial como observador del planeta usandoObservable.removeListener(). De lo contrario, a medida que la nave espacial continúa volando a través del espacio, la memoria se perderá. Eventualmente, el juego no puede manejar los cinco mil millones de planetas observados y se estrellará con unOutOfMemoryError.

Tenga en cuenta que para la gran mayoría de los oyentes y manejadores de eventos de JavaFX, su ciclo de vida es paralelo al de susObservable por lo que el desarrollador de la aplicación no tiene nada de qué preocuparse. Por ejemplo, podríamos construir unTextField y registrarse en el campo de textotextProperty un oyente que valida la entrada del usuario. Mientras el campo de texto se mantenga, queremos que el oyente se quede. Tarde o temprano, el campo de texto ya no se usa y cuando se recolecta la basura, el oyente de validación también se recolecta.

Gestion automatica

Para continuar con el ejemplo de simulación de espacio, suponga que nuestro juego tiene soporte multijugador limitado y que todos los jugadores deben observarse entre sí. Tal vez cada jugador mantenga un tablero de puntuación local con métricas de muertes o tal vez deba observar los mensajes de chat transmitidos. La razón no es el punto importante aquí. ¿Qué pasaría cuando un jugador abandone el juego? Claramente, si los oyentes no son manejados explícitamente (remoto), entonces el jugador que renunció no será elegible para la finalización. El otro jugador mantendrá una fuerte referencia al jugador fuera de línea. La eliminación explícita de los oyentes seguiría siendo una opción válida y probablemente la opción más preferida para nuestro juego, pero digamos que se siente un poco molesto y queremos encontrar una solución más elegante.

Sabemos que el motor del juego mantiene fuertes referencias a todos los jugadores en línea, mientras estén en línea. Así que queremos que las naves espaciales escuchen los cambios o eventos de cada uno solo mientras el motor del juego mantenga las referencias sólidas. Si lees la sección de "teoría", entonces seguramente unWeakReference Suena como una solución.

Sin embargo, solo envolver algo en una WeakReference no es la solución completa. Rara vez lo es. Es cierto que cuando la última referencia segura al "referente" se establece ennull o, de lo contrario, no se puede alcanzar, el referente será elegible para la recolección de basura (asumiendo que el referente no puede ser alcanzado usando unSoftReference). Pero el WeakReference todavía está dando vueltas. El desarrollador de la aplicación debe agregar algunas tuberías para que la propia WeakReference se elimine de la estructura de datos en la que se instaló. De lo contrario, es posible que hayamos reducido la gravedad de la pérdida de memoria, pero todavía habrá una pérdida de memoria porque se agregó dinámicamente. Las referencias consumen memoria también.

Por suerte para nosotros, JavaFX agregó una interfaz.WeakListener y claseWeakEventHandler Como un mecanismo para la "eliminación automática". Los constructores de todas las clases relacionadas aceptan el escucha / controlador real tal como lo proporciona el código del cliente, pero almacenan el escucha / controlador utilizando una referencia débil.

Si nos fijamos en el JavaDoc deWeakEventHandler, notarás que la clase implementaEventHandler, por lo que se puede usar WeakEventHandler donde se espera un EventHandler. Asimismo, una implementación conocida de unWeakListener se puede utilizar en cualquier lugarInvalidationListener o unChangeListener se espera.

Si nos fijamos en el código fuente deWeakEventHandler, te darás cuenta de que la clase es básicamente solo una envoltura. Cuando su referente (el verdadero controlador de eventos) es basura recolectada, laWeakEventHandler "Deja de trabajar" al no hacer nada en absoluto cuandoWeakEventHandler.handle() se llama. losWeakEventHandler no sabe con qué objeto se ha conectado, e incluso si lo hizo, la eliminación de un controlador de eventos no es homogénea. Todas las clases de implementación conocidas deWeakListener Aunque tiene una ventaja competitiva. Cuando se invocan sus devoluciones de llamada, se les proporciona de forma implícita o explícita una referencia a laObservable están registrados con. Así que cuando el referente de unWeakListener se recolecta la basura, eventualmente laWeakListener implementación asegurará que laWeakListener se elimina de laObservable.

Si aún no está claro, la solución para nuestro juego de simulación espacial sería permitir que el motor del juego utilice referencias sólidas para todas las naves espaciales en línea. Cuando una nave espacial se pone en línea, todas las demás naves espaciales en línea se registran con el nuevo jugador utilizando un oyente débil, comoWeakInvalidationListener. Cuando un jugador se desconecta, el motor del juego elimina su fuerte referencia al jugador y el jugador será elegible para la recolección de basura. El motor del juego no tiene que preocuparse por la eliminación explícita del jugador fuera de línea como oyente de los otros jugadores.

Caso 2

No. Para entender mejor lo que voy a decir a continuación, lea primero la respuesta de mi caso 1.

BooleanPropertyBase almacenar una fuerte referencia aotherBool. Esto en sí mismo no causaotherBool ser siempre accesible y, por lo tanto, potencialmente causar una pérdida de memoria. Cuandobool se vuelve inalcanzable, entonces también lo hacen todas sus referencias almacenadas (asumiendo que no se almacenan en ningún otro lugar).

BooleanPropertyBase También funciona agregándose como unaObserver de la propiedad a la que la obligas. Sin embargo, lo hace envolviéndose en una clase que funciona casi exactamente como laWeakListenerSe describe en mi caso 1 respuesta. Así que una vez que anulasbool, será solo cuestión de tiempo antes de que se elimine deotherBool.

 Martin Andersson05 sept. 2017 21:42
La respuesta para el caso 2 es un "no". La referencia posterior al caso 1 se colocó allí solo para los nuevos lectores que se desplazan directamente al caso 2 como una indicación de que deberían leer primero el caso 1 para comprender mejor la explicación en el caso 2. Esto se complica por naturaleza y odio que no lo haya hecho. Se ha podido simplificar más el texto. Al mismo tiempo, siento que el tema merece ser escrito usando un lenguaje técnicamente correcto que cubra todos los aspectos, porque de lo contrario, dejar huecos solo provocará nuevas preguntas, lo que hará que el lector sea aún más inseguro y confuso.
 Pavel_K05 sept. 2017 21:44
Veo. Gracias por su explicación.
 Pavel_K03 sept. 2017 13:38
A decir verdad no entiendo esta respuesta. Caso 1 - no o sí. Caso 2 - no, ver caso 1.
 Vladimir M.21 feb. 2018 09:55
¿Esto se aplica también a los enlaces personalizados? Por ejemplo, si creo un enlace con Bindings.createBooleanBinding (), no es necesario desvincular las dependencias antes de que se recolecte la basura, ¿no?
 Martin Andersson05 sept. 2017 21:50
Creo que es necesario que la mayoría de nosotros volvamos a leer una respuesta como esta un par de veces para poder entenderla, y probablemente hagamos un googlear extenso en el lateral. Es de esperar que la respuesta tenga sentido, pero puede editarla si encuentra que se pueden realizar mejoras. ¡La simplificación es siempre una mejora! =) Además, avíseme si no puede encontrar una manera de entenderlo. Si es así, intentaré encontrar una alternativa.
 Pavel_K05 sept. 2017 22:08
Muchas gracias por tu deseo de ayudar. Creo que es una de las cualidades más importantes de un hombre: el deseo de ayudar a alguien. Después de leerlo hace algún tiempo, llegué a la conclusión de que la situación con los oyentes de propiedades y los enlaces (en particular con los enlaces bidireccionales) no es tan clara. Afortunadamente, uso el patrón MVVM, y tengo vista y modelo de vista en el componente que están estrechamente relacionados con sus propiedades. Sin embargo, el modelo no tiene propiedades fx, así que cuando destruyo mi componente no me importa si la vista está aún vinculada a las propiedades de viewmodel o no. Los destruyo a ambos.

caso 1 respuesta, pero elcaso 2 Es un poco más complicado.The bool.unbind() la llamada es necesaria Si se omite,hace causar una pequeña pérdida de memoria.

Si ejecuta el siguiente bucle, la aplicación eventualmente se quedará sin memoria.

BooleanProperty p1 = new SimpleBooleanProperty();
while(true) {
    BooleanProperty p2 = new SimpleBooleanProperty();
    p2.bind(p1)
}

La BooleanPropertyBase, intenalmente, no utiliza un WeakListener real (una implementación delWeakListener interfaz), está utilizando una solución semicocida. Todas las instancias de "p2" eventualmente se recolectan en la basura, pero un oyente que tiene una WeakReference vacía permanece en la memoria para siempre para cada "p2". Lo mismo se aplica a todas las propiedades, no solo a BooleanPropertyBase.Se explica aqui En detalle, y dicen que está arreglado en Java 9.

En la mayoría de los casos, no observa esta pérdida de memoria, ya que solo deja unas pocas docenas de bytes por cada enlace que no se haya liberado. Pero en algunos casos me causó un verdadero problema. Un buen ejemplo son las celdas de tabla de una tabla que se actualiza con frecuencia. Las celdas luego se vuelven a unir a diferentes propiedades todo el tiempo, y estas sobras en la memoria se acumulan rápidamente.

 Lonely Neuron17 abr. 2018 03:05
¿Estás haciendo referencia a la respuesta por @Martin Andersson?

Su respuesta a la pregunta