Usar la vista Memory
Aprende a usar la vista Memory de DevTools.
La vista Memory ofrece información detallada sobre la asignación de memoria de la aplicación y herramientas para detectar y depurar problemas específicos.
Para obtener información sobre cómo ubicar las pantallas de DevTools en diferentes IDEs, consulta la visión general de DevTools.
Para entender mejor la información presente en esta página, la primera sección explica cómo gestiona la memoria Dart. Si ya entiendes la gestión de memoria de Dart, puedes saltar a la Guía de la vista Memory.
Razones para usar la vista Memory
#Usa la vista Memory para la optimización preventiva de la memoria o cuando tu aplicación experimente una de las siguientes condiciones:
- Se cierra inesperadamente cuando se queda sin memoria
- Se vuelve lenta
- Hace que el dispositivo se vuelva lento o deje de responder
- Se apaga porque excedió el límite de memoria, impuesto por el sistema operativo
-
Exceeds memory usage limit
- Este límite puede variar según el tipo de dispositivos a los que se dirija tu app.
- Sospechar de una fuga de memoria
Conceptos básicos de memoria
#Los objetos de Dart creados mediante el constructor de una clase (por ejemplo, al usar MyClass()) residen en una porción de memoria llamada heap. La memoria en el heap es gestionada por la Dart VM (máquina virtual). La Dart VM asigna memoria para el objeto en el momento de su creación y libera (o desasigna) la memoria cuando el objeto ya no se utiliza (consulta Dart garbage collection).
Tipos de objetos
#Objeto desechable (disposable)
#Un objeto desechable (disposable) es cualquier objeto de Dart que define un método dispose(). Para evitar fugas de memoria, invoca dispose cuando el objeto ya no sea necesario.
Objeto de riesgo de memoria
#Un objeto de riesgo de memoria es un objeto que podría causar una fuga de memoria si no se desecha adecuadamente o si se desecha pero el recolector de basura (GC) no lo recicla.
Objeto raíz, ruta de retención y alcanzabilidad
#Objeto raíz
#Cada aplicación de Dart crea un objeto raíz que referencia, directa o indirectamente, a todos los demás objetos que la aplicación asigna.
Alcanzabilidad
#Si, en algún momento de la ejecución de la aplicación, el objeto raíz deja de referenciar a un objeto asignado, el objeto pasa a estar inalcanzable (unreachable), lo que indica al recolector de basura (GC) que debe desasignar la memoria del objeto.
Ruta de retención
#La secuencia de referencias desde la raíz hasta un objeto se denomina la ruta de retención del objeto, ya que retiene la memoria del objeto evitando que el recolector de basura la libere. Un objeto puede tener muchas rutas de retención. Los objetos con al menos una ruta de retención se denominan objetos alcanzables.
Ejemplo
#El siguiente ejemplo ilustra los conceptos:
class Child{}
class Parent {
Child? child;
}
Parent parent1 = Parent();
void myFunction() {
Child? child = Child();
// The `child` object was allocated in memory.
// It's now retained from garbage collection
// by one retaining path (root …-> myFunction -> child).
Parent? parent2 = Parent()..child = child;
parent1.child = child;
// At this point the `child` object has three retaining paths:
// root …-> myFunction -> child
// root …-> myFunction -> parent2 -> child
// root -> parent1 -> child
child = null;
parent1.child = null;
parent2 = null;
// At this point, the `child` instance is unreachable
// and will eventually be garbage collected.
…
}
Tamaño superficial vs tamaño retenido
#El tamaño superficial incluye solo el tamaño del objeto y sus referencias, mientras que el tamaño retenido también incluye el tamaño de los objetos retenidos.
El tamaño retenido del objeto raíz incluye todos los objetos alcanzables de Dart.
En el siguiente ejemplo, el tamaño de myHugeInstance no forma parte de los tamaños superficiales del elemento primario o secundario, sino que forma parte de sus tamaños retenidos:
class Child{
/// The instance is part of both [parent] and [parent.child]
/// retained sizes.
final myHugeInstance = MyHugeInstance();
}
class Parent {
Child? child;
}
Parent parent = Parent()..child = Child();
En los cálculos de DevTools, si un objeto tiene más de una ruta de retención, su tamaño se asigna como retenido solo a los miembros de la ruta de retención más corta.
En este ejemplo, el objeto x tiene dos rutas de retención:
root -> a -> b -> c -> x
root -> d -> e -> x (shortest retaining path to `x`)
Solo los miembros de la ruta más corta (d y e) incluirán a x en su tamaño retenido.
¿Ocurren fugas de memoria en Dart?
#El recolector de basura no puede prevenir todos los tipos de fugas de memoria, por lo que los desarrolladores aún deben vigilar los objetos para tener un ciclo de vida libre de fugas.
¿Por qué el recolector de basura no puede prevenir todas las fugas?
#Mientras que el recolector de basura se encarga de todos los objetos inalcanzables, es responsabilidad de la aplicación asegurarse de que los objetos innecesarios dejen de ser alcanzables (referenciados desde la raíz).
Por lo tanto, si los objetos innecesarios se dejan referenciados (en una variable global o estática, o como un campo de un objeto de larga vida), el recolector de basura no puede reconocerlos, la asignación de memoria crece progresivamente y la app finalmente se cierra inesperadamente con un error out-of-memory.
Por qué las closures requieren atención extra
#Un patrón de fuga difícil de detectar está relacionado con el uso de closures. En el siguiente código, una referencia a myHugeObject (diseñado para ser de corta vida) se almacena implícitamente en el contexto del closure y se pasa a setHandler. Como resultado, myHugeObject no será reciclado por el recolector de basura mientras handler sea alcanzable.
final handler = () => print(myHugeObject.name);
setHandler(handler);
Por qué BuildContext requiere atención extra
#
Un ejemplo de objeto grande y de corta vida que podría colarse en un área de larga vida y provocar fugas es el parámetro context que se pasa al método build de Flutter.
El siguiente código es propenso a fugas, ya que useHandler podría almacenar el handler en un área de larga vida:
// BAD: DO NOT DO THIS
// This code is leak prone:
@override
Widget build(BuildContext context) {
final handler = () => apply(Theme.of(context));
useHandler(handler);
…
¿Cómo solucionar el código propenso a fugas?
#El siguiente código no es propenso a fugas, porque:
- El closure no utiliza el objeto
contextgrande y de corta vida. - El objeto
theme(usado en su lugar) es de larga vida. Se crea una sola vez y se comparte entre las instancias deBuildContext.
// GOOD
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final handler = () => apply(theme);
useHandler(handler);
…
Regla general para BuildContext
#
En general, usa la siguiente regla para un BuildContext: si el closure no vive más que el widget, no hay problema en pasar el context al closure.
Los widgets Stateful requieren atención extra. Constan de dos clases: el widget y el State del widget, donde el widget es de corta vida y el State es de larga vida. El build context, propiedad del widget, nunca debe ser referenciado desde los campos del State, ya que el State no será reciclado por el recolector de basura junto con el widget y puede vivir significativamente más que este.
Fuga de memoria vs saturación de memoria (memory bloat)
#En una fuga de memoria, una aplicación utiliza memoria progresivamente, por ejemplo, creando repetidamente un listener pero sin desecharlo.
La saturación de memoria (memory bloat) utiliza más memoria de la necesaria para un rendimiento óptimo, por ejemplo, utilizando imágenes excesivamente grandes o manteniendo streams abiertos durante toda su vida útil.
Tanto las fugas como las saturaciones, cuando son grandes, hacen que la aplicación se cierre inesperadamente con un error out-of-memory. Sin embargo, es más probable que las fugas causen problemas de memoria, porque incluso una fuga pequeña, si se repite muchas veces, provoca un fallo.
Guía de la vista Memory
#La vista Memory de DevTools te ayuda a investigar las asignaciones de memoria (tanto en el heap como externas), las fugas de memoria, la saturación de memoria y más. La vista cuenta con las siguientes características:
- Gráfico expandible
-
Obtén un rastreo de alto nivel de la asignación de memoria y visualiza tanto eventos estándar (como la recolección de basura) como eventos personalizados (como la asignación de imágenes).
- Pestaña Profile Memory
-
Consulta la asignación actual de memoria organizada por clase y tipo de memoria.
- Pestaña Diff Snapshots
Detectar e investigar los problemas de gestión de memoria de una función.
- Pestaña Trace Instances
-
Investiga la gestión de memoria de una función para un conjunto específico de clases.
Gráfico expandible
#El gráfico expandible ofrece las siguientes funciones:
Anatomía de la memoria
#Un gráfico de series temporales visualiza el estado de la memoria de Flutter en intervalos sucesivos de tiempo. Cada punto de datos en el gráfico corresponde a la marca de tiempo (eje x) de las cantidades medidas (eje y) del heap. Por ejemplo, se capturan el uso, la capacidad, la memoria externa, la recolección de basura y el tamaño del conjunto residente (resident set size).
Gráfico general de la memoria
#El gráfico general de memoria es un gráfico de series temporales de las estadísticas de memoria recopiladas. Presenta visualmente el estado del heap de Dart o Flutter y la memoria nativa de Dart o Flutter a lo largo del tiempo.
El eje x del gráfico es una línea de tiempo de eventos (series temporales). Todos los datos graficados en el eje y tienen una marca de tiempo de cuándo fueron recopilados. En otras palabras, muestra el estado muestreado de la memoria (capacidad, usada, externa, RSS (resident set size) y GC (recolección de basura)) cada 500 ms. Esto ayuda a ofrecer un aspecto en vivo del estado de la memoria mientras la aplicación se ejecuta.
Al hacer clic en el botón Legend se muestran las mediciones recopiladas, las leyendas y los colores utilizados para presentar los datos.
El eje y de Memory Size Scale se ajusta automáticamente al rango de datos recopilados en el rango visible actual del gráfico.
Las cantidades graficadas en el eje y son las siguientes:
- Heap de Dart/Flutter
Objetos (objetos de Dart y Flutter) en el heap.
- Memoria nativa de Dart/Flutter
-
Memoria que no está en el heap de Dart/Flutter pero que sigue formando parte del consumo total de memoria. Los objetos en esta memoria serían objetos nativos (por ejemplo, al leer un archivo en memoria o una imagen decodificada). Los objetos nativos se exponen a la Dart VM desde el sistema operativo nativo (como Android, Linux, Windows, iOS) mediante un embedder de Dart. El embedder crea un wrapper de Dart con un finalizador, lo que permite que el código de Dart se comunique con estos recursos nativos. Flutter incluye un embedder para Android y iOS. Para obtener más información, consulta Command-line and server apps, Dart on the server with Dart Frog, Custom Flutter Engine Embedders, Dart web server deployment with Heroku.
- Timeline
-
Las marcas de tiempo de todas las estadísticas de memoria y eventos recopilados en un punto específico del tiempo (marca de tiempo).
- Raster Cache
-
El tamaño de las capas o imágenes del raster cache del motor de Flutter al realizar la renderización final después de la composición. Para obtener más información, consulta el Flutter architectural overview y la DevTools Performance view.
- Allocated
-
La capacidad total actual de todos los heaps de Dart. Por lo general, esto es ligeramente mayor que el tamaño total de todos los objetos en el heap.
- RSS - Resident Set Size
-
El resident set size muestra la cantidad de memoria asignada a un proceso. No incluye la memoria que se ha movido al espacio de intercambio (swap). Incluye la memoria de las bibliotecas compartidas que se encuentran cargadas, así como toda la memoria de pila (stack) y de heap. Para obtener más información, consulta Dart VM internals.
Pestaña Profile Memory
#Usa la pestaña Profile Memory para ver la asignación actual de memoria por clase y tipo de memoria. Para un análisis más profundo en Hojas de cálculo de Google u otras herramientas, descarga los datos en formato CSV. Activa Refresh on GC para ver la asignación en tiempo real.
Pestaña Diff Snapshots
#Usa la pestaña Diff Snapshots para investigar la gestión de memoria de una función. Sigue la guía de la pestaña para tomar instantáneas (snapshots) antes y después de interactuar con la aplicación, y compara las diferencias entre ellas:
Toca el botón Filter classes and packages para filtrar los datos:
Para un análisis más profundo en Hojas de cálculo de Google u otras herramientas, descarga los datos en formato CSV.
Pestaña Trace Instances
#Usa la pestaña Trace Instances para investigar qué métodos asignan memoria a un conjunto de clases durante la ejecución de una función:
- Seleccionar clases para rastrear
- Interactúa con tu app para activar el código que te interesa rastrear
- Toca Refresh
- Seleccionar una clase rastreada
- Revisar los datos recopilados
Vista ascendente (bottom up) vs árbol de llamadas (call tree)
#Cambia entre las vistas ascendente (bottom-up) y de árbol de llamadas (call tree) según los detalles de tus tareas.
La vista de árbol de llamadas (call tree) muestra las asignaciones de métodos para cada instancia. La vista es una representación descendente de la pila de llamadas, lo que significa que un método se puede expandir para mostrar sus llamadas secundarias (callees).
La vista ascendente (bottom-up) muestra la lista de diferentes pilas de llamadas que han asignado las instancias.
Otros recursos
#Para obtener más información, consulta los siguientes recursos:
- Para aprender a monitorear el uso de memoria de una app y detectar fugas de memoria usando DevTools, consulta el tutorial guiado de la Memory View.
- Para entender la estructura de memoria de Android, consulta Android: Memory allocation among processes.
A menos que se indique lo contrario, la documentación de este sitio refleja Flutter 3.44.0. Página actualizada por última vez el 2026-05-05. Ver código fuente oreportar un problema.