Articulo de referencia

Resurrección de objetos

En los lenguajes de programación orientados a objetos con recolección de basura , la resurrección de un objeto ocurre cuando un objeto se vuelve accesible (es decir, deja de ser...

En los lenguajes de programación orientados a objetos con recolección de basura , la resurrección de un objeto ocurre cuando un objeto se vuelve accesible (es decir, deja de ser basura) durante el proceso de destrucción de objetos , como efecto secundario de la ejecución de un finalizador .

La resurrección de objetos causa varios problemas , en particular que la posibilidad de que ocurra —incluso si no sucede— hace que la recolección de basura sea significativamente más complicada y lenta, y es una razón importante por la que se desaconsejan los finalizadores. Los lenguajes abordan la resurrección de objetos de diversas maneras . En raras ocasiones, la resurrección de objetos se utiliza para implementar ciertos patrones de diseño , especialmente un grupo de objetos [ 1 ] , mientras que en otras circunstancias la resurrección es un error no deseado causado por un fallo en los finalizadores, y en general se desaconseja su uso [ 2 ] .

Proceso

La resurrección de objetos se produce mediante el siguiente proceso. Primero, un objeto se convierte en basura cuando ya no es accesible desde el programa y puede ser recolectado (destruido y liberado). Luego, durante la destrucción del objeto, antes de que el recolector de basura lo libere, puede ejecutarse un método finalizador , que a su vez puede hacer que ese objeto u otro objeto basura (accesible desde el objeto con un finalizador) vuelva a ser accesible mediante la creación de referencias a él, ya que un finalizador puede contener código arbitrario. Si esto sucede, el objeto referenciado —que no es necesariamente el objeto finalizado— deja de ser basura y no puede liberarse, ya que de lo contrario las referencias a él se convertirían en referencias colgantes y causarían errores al usarse, generalmente un fallo del programa o un comportamiento impredecible. En cambio, para mantener la seguridad de la memoria , el objeto se devuelve a la vida o se resucita.

Para detectar esto, un recolector de basura generalmente realiza una recolección en dos fases en presencia de finalizadores: primero finaliza cualquier basura que tenga un finalizador, y luego vuelve a verificar toda la basura (o toda la basura accesible desde los objetos con finalizadores), por si los finalizadores han resucitado alguna basura. Esto añade sobrecarga y retrasa la recuperación de memoria.

Objetos resucitados

Un objeto resucitado puede ser tratado igual que los demás o recibir un trato especial. En muchos lenguajes, especialmente en C#, Java y Python (a partir de Python 3.4), los objetos se finalizan solo una vez para evitar que se resuciten repetidamente o incluso que sean indestructibles. En C#, los objetos con finalizadores por defecto se finalizan solo una vez, pero pueden volver a registrarse para su finalización. En otros casos, los objetos resucitados se consideran errores, sobre todo en Objective-C, o se tratan de forma idéntica a los demás, como en Python antes de la versión 3.4.

Un objeto resucitado a veces se llamaObjeto zombi ozombi, término que se utiliza para referirse a diversos estados de objetos relacionados con su destrucción, dependiendo del lenguaje y del autor. EnObjective-C, un "objeto zombi" tiene un significado específico que se detalla a continuación. Los objetos zombi son, en cierto modo, análogos alos procesos zombi, ya que han experimentado un cambio de estado de terminación y están próximos a su desasignación, pero los detalles difieren significativamente.

Variantes

En el .NET Framework , especialmente en C# y VB.NET, la "resurrección de objetos" se refiere al estado de un objeto durante la finalización: el objeto vuelve a la vida (desde ser inaccesible), se ejecuta el finalizador y luego se vuelve a ser inaccesible (y ya no se registra para su finalización futura). En .NET, no se realiza un seguimiento de los objetos que necesitan finalización, sino que se almacenan en una "cola" de finalización, [ a ] por lo que, en lugar de hablar de objetos resucitados en el sentido de este artículo, se habla de objetos "en cola para su finalización". Además, los objetos pueden volver a ponerse en cola para su finalización mediante GC.ReRegisterForFinalize, teniendo cuidado de no poner en cola varios objetos. [ 2 ]

Mecanismo

Existen dos formas principales en que un objeto puede resucitarse a sí mismo o a otro objeto: creando una referencia a sí mismo en un objeto al que pueda acceder (la basura no es accesible, pero puede hacer referencia a objetos que no son basura), o creando una referencia en el entorno ( variables globales o, en algunos casos, variables estáticas o variables en un cierre ). A continuación se muestran ejemplos en Python de ambos casos, para un objeto que se resucita a sí mismo. También es posible que un objeto resucite otros objetos si ambos se están recolectando en un ciclo de recolección de basura determinado, mediante los mismos mecanismos.

Se regenera creando una referencia en un objeto al que puede acceder:

clase Clingy : def __init __ ( self , ref = None ) - > None : self.ref = refdef __del__ ( self ): if self . ref : self . ref . ref = self print ( "¡No me dejes!" )a = Clingy ( Clingy ()) # Crea una lista enlazada de 2 elementos, # referenciada por |a| a . ref . ref = a # Crea un ciclo a . ref = None # Borrar la referencia del primer nodo # al segundo hace que el segundo sea basura a . ref = None

Se renueva creando un referente en el entorno global:

c = None class Immortal : def __del__ ( self ): global c c = self print ( "Todavía no estoy muerto." )c = Inmortal () c = Ninguno # Borrar |c| convierte el objeto en basura c = Ninguno

En los ejemplos anteriores, en CPython anterior a la versión 3.4, estos finalizadores se ejecutarán repetidamente y los objetos no serán recolectados por el recolector de basura, mientras que en CPython 3.4 y versiones posteriores, los finalizadores solo se llamarán una vez y los objetos serán recolectados por el recolector de basura la segunda vez que se vuelvan inaccesibles.

Problemas

La resurrección de objetos causa una gran cantidad de problemas.

Complica la recolección de basura
La posibilidad de que un objeto resucite implica que el recolector de basura debe comprobar si hay objetos resucitados después de la finalización, incluso si esto no ocurre realmente, lo que complica y ralentiza la recolección de basura.
objetos indestructibles
En ciertas circunstancias, un objeto puede ser indestructible: si un objeto se resucita en su propio finalizador (o un grupo de objetos se resucitan mutuamente como resultado de sus finalizadores), y el finalizador se llama siempre al destruir el objeto, entonces el objeto no puede ser destruido y su memoria no puede ser recuperada.
Resurrección accidental y fugas
En tercer lugar, la resurrección de objetos puede ser involuntaria, y el objeto resultante puede ser basura semántica, por lo que nunca se recolecta realmente, lo que provoca una fuga de memoria lógica .
Estado inconsistente y reinicialización
Un objeto resucitado puede encontrarse en un estado inconsistente o violar las invariantes de clase , debido a que el finalizador se ha ejecutado y ha provocado un estado irregular. Por lo tanto, los objetos resucitados generalmente necesitan ser reinicializados manualmente. [ 1 ]
Finalización o refinalización única
En algunos lenguajes (como Java y Python 3.4+), la finalización está garantizada para ocurrir exactamente una vez por objeto, por lo que los objetos resucitados no tendrán sus finalizadores llamados; por lo tanto, los objetos resucitados deben ejecutar cualquier código de limpieza necesario fuera del finalizador. En otros lenguajes, el programador puede forzar que la finalización se realice repetidamente; en particular, C# tiene GC.ReRegisterForFinalize. [ 1 ]

Soluciones

Los lenguajes de programación han adoptado varios métodos diferentes para lidiar con la resurrección de objetos, el más común es mediante la recolección de basura en dos fases en presencia de finalizadores, para evitar referencias colgantes; y finalizando los objetos solo una vez, particularmente marcándolos como finalizados (a través de una bandera), para garantizar que los objetos puedan ser destruidos.

Java no liberará el objeto hasta que haya demostrado que el objeto vuelve a ser inaccesible, pero no ejecutará el finalizador más de una vez. [ 3 ]

En Python, antes de Python 3.4, la implementación estándar de CPython trataba los objetos resucitados de forma idéntica a otros objetos (que nunca se habían finalizado), lo que hacía posible la existencia de objetos indestructibles. [ 4 ] Además, no recolectaba basura de los ciclos que contenían un objeto con un finalizador, para evitar posibles problemas con la resurrección de objetos. A partir de Python 3.4, el comportamiento es prácticamente el mismo que en Java: [ b ] los objetos solo se finalizan una vez (se marcan como "ya finalizados"), la recolección de basura de los ciclos se realiza en dos fases, y la segunda fase comprueba si hay objetos resucitados. [ 5 ] [ 6 ]

Objective-C 2.0 pondrá los objetos resucitados en un estado "zombie", donde registran todos los mensajes que se les envían, pero no hacen nada más. [ 7 ] Véase también Conteo automático de referencias: puesta a cero de referencias débiles para el manejo de referencias débiles .

En el .NET Framework, especialmente en C# y VB.NET, la finalización de objetos se determina mediante una "cola" de finalización, [ a ] que se comprueba durante la destrucción del objeto. Los objetos con un finalizador se colocan en esta cola al crearse y se extraen cuando se llama al finalizador, pero se pueden extraer manualmente (antes de la finalización) con SuppressFinalizeo volver a encolar con ReRegisterForFinalize. Por lo tanto, por defecto, los objetos con finalizadores se finalizan como máximo una vez, pero esta finalización se puede suprimir, o los objetos se pueden finalizar varias veces si se resucitan (se vuelven a hacer accesibles) y luego se vuelven a encolar para su finalización. Además, las referencias débiles por defecto no rastrean la resurrección, lo que significa que una referencia débil no se actualiza si un objeto se resucita; estas se denominan referencias débiles cortas , y las referencias débiles que rastrean la resurrección se denominan referencias débiles largas . [ 8 ]

Aplicaciones

La resurrección de objetos es útil para gestionar un grupo de objetos de uso común, pero oscurece el código y lo hace más confuso. [ 3 ] Debería usarse solo para objetos que se utilicen con frecuencia y cuya creación/destrucción consuma mucho tiempo. Un ejemplo podría ser un array de números aleatorios, donde se crea y destruye una gran cantidad de ellos en poco tiempo, pero donde en realidad solo se utiliza una pequeña cantidad al mismo tiempo. Con la resurrección de objetos, una técnica de agrupación reduciría la sobrecarga innecesaria de creación y destrucción. En este caso, un gestor de grupos obtendría en su pila de objetos información en forma de referencia al objeto, si este se va a destruir en ese momento. El gestor de grupos conservará el objeto para su reutilización posterior. [ 9 ]

Véase también

Notas

  1. 1 2 Esto no es estrictamente una cola, ya que los elementos se pueden eliminar del medio medianteGC.SuppressFinalization.
  2. CPython utiliza recuentos de referencias para la basura no cíclica, con un detector de ciclos separado, mientras que la mayoría de las implementaciones de Java utilizan un recolector de basura de rastreo.

Referencias

  1. 1 2 3 Goldshtein, Zurbalev y Flatow 2012 , pág. 129 . 
  2. 1 2 Richter 2000 .
  3. 1 2 "¿Qué es la resurrección (en la recolección de basura)?" . XYZWS. Archivado del original el 23-11-2011 . Recuperado el 01-08-2011 . Un objeto que ha sido elegible para la recolección de basura puede dejar de ser elegible y volver a la vida normal. Dentro de un método finalize(), puede asignar esto a una variable de referencia y evitar la recolección de ese objeto, un acto que muchos desarrolladores llaman resurrección. /El método finalize() nunca es llamado más de una vez por la JVM para un objeto dado. La JVM no invocará el método finalize() nuevamente después de la resurrección (ya que el método finalize() ya se ejecutó para ese objeto).
  4. Respuesta de Tim Peters a "¿ Cuántas veces se puede llamar a `__del__` por objeto en Python? "
  5. Novedades de Python 3.4 , PEP 442: Finalización segura de objetos
  6. Pitrou, Antoine (2013). "PEP 442 -- Finalización segura de objetos" .
  7. Implementación de un método finalize
  8. Goldshtein, Zurbalev y Flatow 2012 , pág. 131 . 
  9. "Resurrección de objetos" (PDF) . Hesab.net . Consultado el 1 de agosto de 2011. La resurrección de objetos es una técnica avanzada que probablemente solo sea útil en escenarios inusuales, como cuando se implementa un grupo de objetos cuya creación y destrucción consume mucho tiempo. ... La aplicación de demostración ObjectPool muestra que un administrador de grupo de objetos puede mejorar el rendimiento cuando se crean y destruyen muchos objetos con frecuencia. Supongamos que tenemos una clase RandomArray, que encapsula una matriz de números aleatorios. El programa principal crea y destruye miles de objetos RandomArray, aunque solo unos pocos objetos estén activos en un momento dado. Debido a que la clase crea la matriz aleatoria en su método constructor (una operación que consume mucho tiempo), esta situación es ideal para una técnica de agrupación. ... El punto crucial en la técnica de agrupación es que la clase PoolManager contiene una referencia a los objetos no utilizados en el grupo (en el objeto PooledObjects Stack), pero no a los objetos que está utilizando el programa principal. De hecho, estos últimos objetos se mantienen activos solo mediante referencias en el programa principal. Cuando el programa principal asigna el valor Nothing a un objeto RandomArray (o lo deja fuera de ámbito) y se produce una recolección de basura, el recolector invoca el método Finalize del objeto. Por lo tanto, el código dentro del método Finalize de RandomArray tiene la oportunidad de recuperarse almacenando una referencia a sí mismo en la estructura PooledObjects del PoolManager. Así, cuando se vuelve a llamar a la función NewRandomArray, el objeto PoolManager puede devolver un objeto agrupado al cliente sin tener que pasar por el proceso, que consume mucho tiempo, de crear uno nuevo.
  • Goldshtein, Sasha; Zurbalev, Dima; Flatow, Ido (2012). Pro .NET Performance: Optimize Your C# Applications . Apress. ISBN 978-1-4302-4458-5.
  • Richter, Jeffrey (noviembre de 2000). "Recolección de basura: administración automática de memoria en Microsoft .NET Framework" . Revista MSDN .