Entendiendo la recolección de basura en AS3

¿Alguna vez has usado una aplicación Flash y has notado un retraso en ella? ¿Aún no sabes por qué ese juego flash se ejecuta lentamente en tu computadora? Si desea saber más sobre una posible causa, este artículo es para usted..

Encontramos a este increíble autor gracias a FlashGameLicense.com, el lugar para comprar y vender juegos Flash.!

Tutorial republicado

Cada pocas semanas, revisamos algunas de las publicaciones favoritas de nuestros lectores de toda la historia del sitio. Este tutorial fue publicado por primera vez en junio de 2010..


Vista previa del resultado final

Echemos un vistazo al resultado final en el que trabajaremos:


Paso 1: Una ejecución rápida a través de referencias

Antes de entrar en el tema real, primero debe saber un poco acerca de cómo funcionan las instancias y referencias en AS3. Si ya lo has leído, todavía recomiendo leer este pequeño paso. De esa manera, todo el conocimiento estará fresco en tu cabeza y no tendrás problemas para leer el resto de esta Sugerencia rápida!

La creación y referencia de instancias en AS3 es diferente de lo que la mayoría de la gente piensa. La creación de instancias (o "creación") de algo ocurre solo cuando el código solicita crear un objeto. Por lo general, esto sucede a través de la palabra clave "nueva", pero también está presente cuando usa una sintaxis literal o definir parámetros para funciones, por ejemplo. A continuación se muestran ejemplos de esto:

 // Creación de instancias a través de la palabra clave "nuevo" nuevo Objeto (); nueva matriz (); nuevo int (); nueva cadena (); nuevo booleano (); nueva fecha (); // Creación de instancias a través de sintaxis literal ; []; 5 "¡Hola mundo!" verdadero // Creación de instancias a través de parámetros de función función privada tutExample (parámetro1: int, parámetro2: booleano): void

Después de crear un objeto, permanecerá solo hasta que algo lo haga referencia. Para hacer eso, generalmente creas una variable y pasas el valor del objeto a la variable, para que sepa qué objeto tiene actualmente. Sin embargo (y esta es la parte que la mayoría de la gente no sabe), cuando pasa el valor de una variable a otra variable, no está creando un nuevo objeto. ¡En lugar de eso, está creando otro enlace al objeto que ambas variables contienen ahora! Vea la imagen de abajo para aclaración:

La imagen asume ambos Variable 1 y Variable 2 puede sostener el emoticono (es decir, pueden tener el mismo tipo). En el lado izquierdo, solo. Variable 1 existe Sin embargo, cuando creamos y configuramos Variable 2 al mismo valor de Variable 1, No estamos creando un enlace entre Variable 1 y Variable 2 (parte superior derecha de la imagen), en su lugar, estamos creando un enlace entre el Smiley y Variable 2 (parte inferior derecha de la imagen).

Con este conocimiento, podemos saltar al recolector de basura..


Paso 2: cada ciudad necesita un recolector de basura

Es obvio que cada aplicación necesita una cierta cantidad de memoria para ejecutarse, ya que necesita variables para mantener los valores y usarlos. Lo que no está claro es cómo la aplicación administra los objetos que ya no son necesarios. ¿Los recicla? ¿Los borra? ¿Deja el objeto en la memoria hasta que se cierra la aplicación? Las tres opciones pueden suceder, pero aquí hablaremos específicamente sobre la segunda y la tercera..

Imagine una situación en la que una aplicación crea una gran cantidad de objetos cuando se inicializa, pero una vez que este período termina, más de la mitad de los objetos creados permanecen sin usar. ¿Qué pasaría si se dejaran en la memoria? Sin duda, ocuparían mucho espacio, lo que provocaría lo que la gente llama retraso, que es una notable desaceleración en la aplicación. A la mayoría de los usuarios no les gustaría esto, por lo que debemos evitarlo. ¿Cómo podemos codificar para que la aplicación se ejecute de manera más eficiente? La respuesta está en el Recolector de basura.

El recolector de basura es una forma de gestión de memoria. Su objetivo es eliminar cualquier objeto que no se use y está ocupando espacio en la memoria del sistema. De esta manera la aplicación puede ejecutarse con un mínimo de uso de memoria. Vamos a ver cómo funciona:

Cuando su aplicación comienza a ejecutarse, solicita una cantidad de memoria del sistema que será utilizada por la aplicación. La aplicación se inicia y luego llena esta memoria con cualquier información que necesite; cada objeto que creas entra en él. Sin embargo, si el uso de la memoria se acerca a la memoria solicitada inicialmente, el recolector de basura se ejecuta, buscando cualquier objeto que no se use para vaciar un poco de espacio en la memoria. A veces esto causa un poco de retraso en la aplicación, debido a la gran sobrecarga de búsqueda de objetos.

En la imagen, se puede ver el picos de memoria (en círculo en verde). Los picos y la caída repentina son causados ​​por el recolector de basura, que actúa cuando la aplicación ha alcanzado el uso de memoria solicitado (la línea roja), eliminando todos los objetos innecesarios..


Paso 3: Iniciar el archivo SWF

Ahora que sabemos lo que el recolector de basura puede hacer por nosotros, es hora de aprender a codificar para obtener todos los beneficios. En primer lugar, necesitamos saber cómo funciona el recolector de basura, desde una perspectiva práctica. En el código, los objetos se vuelven elegibles para la recolección de basura cuando se vuelven inalcanzables. Cuando no se puede acceder a un objeto, el código entiende que ya no se utilizará, por lo que debe recopilarse.

Actionscript 3 verifica la accesibilidad a través de raíces de recolección de basura. En el momento en que no se puede acceder a un objeto a través de una raíz de recolección de basura, se vuelve elegible para la recolección. A continuación verá una lista de las principales raíces de recolección de basura:

  • Variables a nivel de paquete y estáticas.
  • Variables locales y variables en el alcance de un método o función de ejecución.
  • Variables de instancia desde la instancia de clase principal de la aplicación o desde la lista de visualización.

Para entender cómo los objetos son manejados por el recolector de basura, debemos codificar y examinar lo que está sucediendo en el archivo de ejemplo. Usaré el proyecto AS3 de FlashDevelop y el compilador de Flex, pero supongo que puede hacerlo en cualquier IDE que desee, ya que no usaremos cosas específicas que existen solo en FlashDevelop. He construido un archivo simple con un botón y una estructura de texto. Dado que este no es el objetivo de este rápido consejo, lo explicaré rápidamente: cuando se hace clic en un botón, se activa una función. En cualquier momento que deseamos mostrar algún texto en la pantalla, usted llama a una función con el texto y se muestra. También hay otro campo de texto para mostrar una descripción de los botones..

El objetivo de nuestro archivo de ejemplo es crear objetos, eliminarlos y examinar qué les sucede después de eliminarlos. Necesitaremos una forma de saber si el objeto está vivo o no, por lo que agregaremos un oyente ENTER_FRAME a cada uno de los objetos, y les haremos mostrar un texto con el tiempo que han estado vivos. Así que vamos a codificar el primer objeto!

Creé una imagen divertida y sonriente para los objetos, en homenaje al excelente tutorial del juego Avoider de Michael James Williams, que también utiliza imágenes sonrientes. Cada objeto tendrá un número en su cabeza, por lo que podemos identificarlo. También nombré el primer objeto. TheObject1, y el segundo objeto TheObject2, por lo que será fácil de distinguir. Vayamos al código:

 private var _theObject1: TheObject1; función privada newObjectSimple1 (e: MouseEvent): void // Si ya existe un objeto creado, no haga nada si (_theObject1) devuelve; // Cree el nuevo objeto, ajústelo a la posición en la que debería estar y añádalo a la lista de visualización para que podamos ver que se creó _theObject1 = new TheObject1 (); _theObject1.x = 320; _elObjeto1.y = 280; _theObject1.addEventListener (Event.ENTER_FRAME, changeTextField1); addChild (_theObject1); 

El segundo objeto se ve casi igual. Aquí está:

 private var _theObject2: TheObject2; función privada newObjectSimple2 (e: MouseEvent): void // Si ya existe un objeto creado, no haga nada si (_theObject2) devuelve; // Cree el nuevo objeto, configúrelo en la posición en la que debería estar y añádalo a la lista de visualización para que podamos ver que se creó _theObject2 = new TheObject2 (); _elObjeto2.x = 400; _elObjeto2.y = 280; _theObject2.addEventListener (Event.ENTER_FRAME, changeTextField2); addChild (_theObject2); 

En el codigo, newObjectSimple1 () y newObjectSimple2 () Son funciones que se activan cuando se hace clic en su botón correspondiente. Estas funciones simplemente crean un objeto y lo agregan en la pantalla de visualización, por lo que sabemos que fue creado. Además, crea un ENTER_FRAME escucha de eventos en cada objeto, lo que hará que muestren un mensaje cada segundo, siempre que estén activos. Aquí están las funciones:

 función privada changeTextField1 (e: Event): void // Nuestro ejemplo se está ejecutando a 30FPS, así que agreguemos 1/30 en cada fotograma del recuento. _objectCount1 + = 0.034; // Comprueba si _objectCount1 ha pasado un segundo más si (int (_objectCount1)> _secondCount1) // Muestra un texto en la pantalla displayText ("El objeto 1 está vivo ..." + int (_objectCount1)); _secondCount1 = int (_objectCount1); 
 función privada changeTextField2 (e: Event): void // Nuestro ejemplo se está ejecutando a 30FPS, así que agreguemos 1/30 en cada fotograma del recuento. _objectCount2 + = 0.034; // Comprueba si _objectCount2 ha pasado un segundo más si (int (_objectCount2)> _secondCount2) // Muestra un texto en la pantalla displayText ("El objeto 2 está vivo ..." + int (_objectCount2)); _secondCount2 = int (_objectCount2); 

Estas funciones simplemente muestran un mensaje en la pantalla con la hora en que los objetos han estado vivos. Aquí está el archivo SWF con el ejemplo actual:


Paso 4: Eliminar los objetos

Ahora que hemos cubierto la creación de objetos, intentemos algo: ¿alguna vez te has preguntado qué pasaría si realmente borras (eliminas todas las referencias) un objeto? ¿Se recoge la basura? Eso es lo que vamos a probar ahora. Vamos a construir dos botones de eliminación, uno para cada objeto. Vamos a hacer el código para ellos:

 función privada deleteObject1 (e: MouseEvent): void // Verifique si _theObject1 realmente existe antes de eliminarlo de la lista de visualización si (_theObject1 && contiene (_theObject1)) removeChild (_theObject1); // Eliminando todas las referencias al objeto (esta es la única referencia) _theObject1 = null; // Muestra un texto en la pantalla displayText ("¡Objeto eliminado 1 con éxito!"); 
 función privada deleteObject2 (e: MouseEvent): void // Verifique si _theObject2 realmente existe antes de eliminarlo de la lista de visualización si (_theObject1 && contiene (_theObject2)) removeChild (_theObject2); // Eliminando todas las referencias al objeto (esta es la única referencia) _theObject2 = null; // Muestra un texto en la pantalla displayText ("¡Objeto eliminado 2 con éxito!"); 

Echemos un vistazo al SWF ahora. Qué piensas tú que sucederá?

Como puedes ver. Si hace clic en "Crear objeto1" y luego en "Eliminar objeto1", ¡en realidad no sucede nada! Podemos decir que el código se ejecuta, porque el texto aparece en la pantalla, pero ¿por qué no se elimina el objeto? El objeto todavía está allí porque en realidad no fue eliminado. Cuando borramos todas las referencias, le dijimos al código que lo hiciera elegible para la recolección de basura, pero el recolector de basura nunca se ejecuta. Recuerde que el recolector de basura solo se ejecutará cuando el uso de memoria actual se acerque a la memoria solicitada cuando la aplicación comenzó a ejecutarse. Tiene sentido, pero ¿cómo vamos a probar esto??

Ciertamente no voy a escribir un fragmento de código para llenar nuestra solicitud con objetos inútiles hasta que el uso de la memoria sea demasiado grande. En su lugar, usaremos una función actualmente no admitida por Adobe, según el artículo de Grant Skinner, que obliga al recolector de basura a ejecutarse. De esa manera, podemos activar este método simple y ver qué sucede cuando se ejecuta. Además, de ahora en adelante, me referiré al recolector de basura como GC, por simplicidad. Aquí está la función:

 función privada forceGC (e: MouseEvent): void try new LocalConnection (). connect ('foo'); nuevo LocalConnection (). connect ('foo');  catch (e: *)  // Muestra un texto en la pantalla displayText ("----- Recolección de basura activada -----"); 

Se sabe que esta función simple, que solo crea dos objetos LocalConnection (), obliga al GC a ejecutarse, por lo que lo llamaremos cuando queremos que esto suceda. No recomiendo usar esta función en una aplicación seria. Si lo está haciendo para probar, no hay problemas reales, pero si es para una aplicación que se distribuirá a las personas, esta no es una buena función, ya que puede incurrir en efectos negativos..

Lo que recomiendo para casos como este es que deje que el GC funcione a su propio ritmo. No trates de forzarlo. En su lugar, concéntrese en la codificación de manera eficiente para que no se produzcan problemas de memoria (lo cubriremos en el Paso 6). Ahora, echemos un vistazo a nuestro SWF de ejemplo otra vez, y haga clic en el botón "Recolectar basura" después de crear y eliminar un objeto.

¿Has probado el archivo? ¡Funcionó! Puede ver que ahora, después de eliminar un objeto y activar el GC, ¡elimina el objeto! Observe que si no elimina el objeto y llama al GC, no ocurrirá nada, ya que todavía hay una referencia a ese objeto en el código. Ahora, ¿qué pasa si intentamos mantener dos referencias a un objeto y eliminar una de ellas??


Paso 5: Creando otra referencia

Ahora que hemos demostrado que el GC funciona exactamente como queríamos, intentemos otra cosa: vincule otra referencia a un objeto (Objeto 1) y elimine el original. Primero, debemos crear una función para vincular y desvincular una referencia a nuestro objeto. Vamos a hacerlo:

 función privada saveObject1 (e: MouseEvent): void // _onSave es un Booleano para verificar si debemos vincular o desvincular la referencia if (_onSave) // Si no hay ningún objeto para guardar, no haga nada si (! _theObject1)  // Muestra un texto en la pantalla displayText ("No hay ningún objeto 1 para guardar!"); regreso;  // Una nueva variable para mantener otra referencia a Object1 _theSavedObject = _theObject1; // Muestra un texto en la pantalla displayText ("¡Objeto guardado 1 con éxito!"); // La próxima vez que se ejecute esta función, desvánela, ya que solo vinculamos _onSave = false;  else // Eliminando la referencia a él _theSavedObject = null; // Muestra un texto en la pantalla displayText ("¡Objeto no guardado 1 con éxito!"); // La próxima vez que se ejecute esta función, vincúlala, ya que simplemente desvinculamos _onSave = true; 

Si probamos nuestro swf ahora, notaremos que si creamos Object1, luego lo guardamos, lo eliminamos y forzamos a que el GC se ejecute, no ocurrirá nada. Esto se debe a que ahora, incluso si eliminamos el enlace "original" al objeto, todavía hay otra referencia a él, lo que evita que sea elegible para la recolección de basura. Esto es básicamente todo lo que necesitas saber sobre el recolector de basura. No es un misterio, después de todo. ¿Pero cómo aplicamos esto a nuestro entorno actual? ¿Cómo podemos usar este conocimiento para evitar que nuestra aplicación se ejecute lentamente? Esto es lo que nos mostrará el Paso 6: cómo aplicar esto en ejemplos reales.


Paso 6: Hacer su código eficiente

Ahora, para la mejor parte: ¡hacer que su código funcione con el GC de manera eficiente! Este paso proporcionará información útil que debe conservar durante toda su vida. ¡Guárdelo correctamente! Primero, me gustaría presentar una nueva forma de construir sus objetos en su aplicación. Es una forma simple, pero efectiva de colaborar con el GC. De esta manera se introducen dos clases simples, que se pueden expandir a otras, una vez que entiendas lo que hace.

La idea de esta manera es implementar una función, llamada destroy (), en cada objeto que crees, y llamarla cada vez que termines de trabajar con un objeto. La función contiene todo el código necesario para eliminar todas las referencias hacia y desde el objeto (excluyendo la referencia que se usó para llamar a la función), por lo que se asegura de que el objeto abandone su aplicación totalmente aislado y el GC lo reconozca fácilmente. La razón de esto se explica en el siguiente paso. Veamos el código general de la función:

 // Cree esto en cada objeto que use la función pública destroy (): void // Eliminar detectores de eventos // Quite cualquier cosa en la lista de visualización // Borre las referencias a otros objetos, para que quede completamente aislado // ... // Cuando quiera eliminar el objeto, haga esto: theObject.destroy (); // Y luego nula la última referencia a él theObject = null;

En esta función, tendrás que borrar todo del objeto, para que permanezca aislado en la aplicación. Después de hacer eso, será más fácil para el GC localizar y eliminar el objeto. Ahora veamos algunas de las situaciones en las que ocurren la mayoría de los errores de memoria:

  • Objetos que se utilizan solo en un intervalo de ejecución.: Tenga cuidado con estos, ya que pueden ser los que consumen mucha memoria. Estos objetos existen solo durante un período de tiempo (por ejemplo, para almacenar valores cuando se ejecuta una función) y no se accede a ellos con mucha frecuencia. Recuerde eliminar todas las referencias a ellas una vez que haya terminado con ellas, de lo contrario, puede tener muchas de ellas en su aplicación, solo tomando espacio en la memoria. Tenga en cuenta que si crea muchas referencias a ellas, debe eliminar cada una de ellas a través de destruir() función.
  • Objetos dejados en la lista de visualización: siempre elimine un objeto de la lista de visualización si desea eliminarlo. La lista de visualización es uno de los raíces de recolección de basura (¿recuerdas eso?) y, por lo tanto, es realmente importante que mantengas tus objetos alejados cuando los elimines.
  • Referencias de etapa, padre y raíz: si te gusta usar mucho estas propiedades, recuerda eliminarlas cuando hayas terminado. Si muchos de sus objetos tienen una referencia a estos, es posible que tenga problemas.!
  • Oyentes de eventos: a veces la referencia que evita que sus objetos se recopilen es un detector de eventos. Recuerde eliminarlos o utilizarlos como oyentes débiles, si es necesario..
  • Arrays y vectores: a veces, sus matrices y vectores pueden tener otros objetos, dejando referencias dentro de ellos de las que quizás no esté al tanto. Cuidado con matrices y vectores.!

Paso 7: La Isla de las Referencias

Aunque trabajar con el GC es genial, no es perfecto. Debe prestar atención a lo que está haciendo, de lo contrario, pueden suceder cosas malas con su aplicación. Me gustaría demostrar un problema que puede surgir si no sigue todos los pasos necesarios para hacer que su código funcione correctamente con el GC.

A veces, si no borra todas las referencias ay desde un objeto, puede tener este problema, especialmente si vincula muchos objetos en su aplicación. A veces, una sola referencia puede ser suficiente para que esto suceda: todos sus objetos forman una isla de referencias, en la que todos los objetos están conectados a otros, sin permitir que el GC los elimine..

Cuando se ejecuta el GC, realiza dos tareas simples para verificar si hay objetos que eliminar. Una de estas tareas es contar cuántas referencias tiene cada objeto. Todos los objetos con 0 referencias se recogen al mismo tiempo. La otra tarea es verificar si hay un pequeño grupo de objetos que se vinculen entre sí, pero que no se pueda acceder, por lo tanto, desperdiciando memoria. Compruebe la imagen:

Como puede ver, no se puede alcanzar los objetos verdes, pero su recuento de referencia es 1. El GC realiza la segunda tarea para verificar esta porción de objetos y los elimina a todos. Sin embargo, cuando el fragmento es demasiado grande, el GC "se da por vencido" al verificar y asume que se puede alcanzar los objetos. Ahora imagina si tienes algo como eso:

Esta es la isla de las referencias. Tomaría mucha memoria del sistema y no sería recopilada por el GC debido a su complejidad. Suena bastante mal, ¿eh? Sin embargo, se puede evitar fácilmente. Solo asegúrate de que hayas borrado todas las referencias hacia y desde un objeto, y entonces no sucederán cosas como esas!


Conclusión

Esto es todo por ahora. En este Consejo rápido, aprendimos que podemos hacer que nuestro código sea mejor y más eficiente para reducir los problemas de demora y memoria, lo que lo hace más estable. Para hacer esto, debemos entender cómo funcionan los objetos de referencia en AS3 y cómo beneficiarnos de ellos para que el GC funcione correctamente en nuestra aplicación. A pesar del hecho de que podemos mejorar nuestra aplicación, debemos tener cuidado al hacerlo; de lo contrario, puede volverse más complicado y lento.!

Espero que te haya gustado este sencillo consejo. Si tiene alguna pregunta, deje un comentario abajo!