Mar 15th, 2025
Una mañana temprano a finales de diciembre de 2024, cuando la mayoría de nosotros ya estábamos mentalmente de vacaciones de Año Nuevo, llegué al trabajo un poco más tarde de lo habitual. Cuando llegué a la oficina y estaba colgando mi atuendo de 20 capas en un perchero, un compañero de trabajo se volvió hacia mí y dijo: «¿Has visto el correo electrónico?». Yo, molesto: «¿Qué correo electrónico?», ya que he hecho de mi misión de vida evitar hacer cosas relacionadas con el trabajo en casa. Él responde: «Joe de NOMBRE-DE-LA-EMPRESA-OCULTADO nos envió un mensaje diciendo que todos sus dispositivos están congelados o se han bloqueado».
El intercambio anterior marca el comienzo de mi búsqueda de fugas de memoria y optimización de aplicaciones en Flutter, que duró dos meses.
Ustedes, que probablemente están pasando por lo mismo, podrían encontrar esta serie de publicaciones en el blog como una bendición. En esta serie de 4 partes, describiré todo lo que he aprendido sobre las fugas de memoria, sus causas y cómo solucionarlas. Primero tocaré un poco de teoría sobre lo que causa las fugas de memoria y cómo evitarlas, y luego continuaré con las formas de detectar tus fugas de memoria, desde las técnicas más simples hasta las más avanzadas.
Con la información que recibas, no te tomará dos meses encontrar y resolver tus fugas de memoria.
Imagina que estás saliendo de una habitación de hotel, y la recepcionista olvida marcar tu habitación como vacía. Aunque esa habitación no esté en uso, nadie más puede usarla.
Imagina que asignas un objeto a un lugar en la memoria y olvidas liberarlo cuando ya no lo necesitas. Aunque ese espacio de memoria podría reutilizarse, nadie más puede usarlo.
Tener un objeto ocupando tu espacio de memoria se llama fuga de memoria.
En resumen, las fugas de memoria causan un alto uso de memoria, ralentizan el rendimiento, provocan bloqueos y, en dispositivos móviles, consumen más batería.
Centrémonos en Flutter y sus funcionalidades de gestión de memoria.
El lenguaje de programación Dart, la base de Flutter, utiliza un mecanismo de recolección de basura generacional (GC) para gestionar el uso de memoria del sistema.
Utiliza un enfoque de dos niveles que se ocupa de los objetos de vida corta y los objetos de vida larga, respectivamente:
Diagrama de flujo del proceso de recolección de basura en Dart
Cualquier objeto sin referencias adjuntas es un objetivo para el GC de Dart. Si un objeto todavía tiene referencias que apuntan a él, incluso cuando ya no se usa, el GC de Dart lo omitirá, y el objeto permanecerá en la memoria. Etiquetémoslos como objetos no recolectables.
Entonces, ¿necesitas hacer algo? ¿El recolector de basura de Dart hace todo el trabajo por ti? ¿Dart se encarga de las asignaciones de memoria no utilizadas?
Sí y no.
Es cierto que, a diferencia de
C
, donde debes encargarte de todas tus asignaciones de memoria tú mismo, Dart hace la mayor parte del trabajo pesado por ti. Sin embargo, hay excepciones para las cuales el GC de Dart no es lo suficientemente inteligente como para encargarse de ellas.
Como Dart es un lenguaje orientado a objetos, significa que todo es un objeto en el fondo, incluso los tipos primitivos como int, double, bool, String y null.
A primera vista, lo harás muy bien si te encargas de todos los objetos Dart con el método dispose en ellos. El desarrollador debe liberar estos objetos "manualmente" cuando el uso previsto del objeto haya terminado.
Pero lo anterior no cubre todo. No todos los objetos no recolectables tienen un método dispose.
Aquí hay una lista de los objetos que más comúnmente generan fugas:
Si estás interesado en ejemplos y plantillas sobre cómo deshacerte de los objetos no recolectables mencionados anteriormente, dirígete a la
de esta serie.