Gestión de memoria JavaScript





¡Buen dia amigos!



En la gran mayoría de los casos, como desarrolladores de JavaScript, no tenemos que preocuparnos por trabajar con memoria. El motor lo hace por nosotros.



Sin embargo, un día se encontrará con un problema llamado "pérdida de memoria", que solo se puede resolver si se sabe cómo se asigna la memoria en JavaScript.



En este artículo, explicaré cómo funcionan la asignación de memoria y la recolección de basura, y cómo evitar algunos de los problemas comunes asociados con las pérdidas de memoria.



Ciclo de vida de la memoria



Cuando crea una variable o función, el motor de JavaScript le asigna memoria y la libera cuando ya no es necesaria.



Asignar memoria es el proceso de reservar un espacio específico en la memoria, y liberar memoria es liberar ese espacio para que pueda usarse para otros fines.



Cada vez que se crea una variable o función, la memoria pasa por las siguientes etapas:







  • Asignación de memoria: el motor asigna automáticamente memoria para el objeto creado
  • Uso de la memoria: leer y escribir datos en la memoria no es más que escribir y leer datos de una variable
  • Liberar memoria: este paso también lo realiza automáticamente el motor. Una vez que se libera la memoria, se puede utilizar para otros fines.


Pila y pila



La siguiente pregunta es ¿qué significa memoria? ¿Dónde se almacenan realmente los datos?



El motor tiene dos de estos lugares: el montón y la pila. Heap y Stack son estructuras de datos que utiliza el motor para diferentes propósitos.



Pila: asignación de memoria estática







Todos los datos del ejemplo se almacenan en la pila porque es una primitiva.



Una pila es una estructura de datos que se utiliza para almacenar datos estáticos. Los datos estáticos son datos cuyo tamaño es conocido por el motor en la etapa de compilación del código. En JavaScript, estos datos son primitivos (cadenas, números, valores booleanos, indefinidos y nulos) y referencias que apuntan a objetos y funciones.



Dado que el motor sabe que el tamaño de los datos no cambiará, asigna un tamaño fijo de memoria para cada valor. El proceso de asignación de memoria antes de ejecutar su código se denomina asignación de memoria estática. Dado que el motor asigna un tamaño fijo de memoria, existen ciertos límites en este tamaño, que dependen en gran medida del navegador.



Montón: asignación de memoria dinámica



El montón es para almacenar objetos y funciones. A diferencia de la pila, el motor no asigna un tamaño fijo de memoria para los objetos. La memoria se asigna según sea necesario. Esta asignación de memoria se llama dinámica. Aquí hay una pequeña tabla de comparación:



Apilar Montón
Valores y referencias primitivas Objetos y funciones
El tamaño se conoce en tiempo de compilación El tamaño se conoce en tiempo de ejecución
Memoria fija asignada El tamaño de la memoria para cada objeto no está limitado


Ejemplos de



Veamos un par de ejemplos.



  const person = {
    name: "John",
    age: 24,
  };


El motor asigna memoria para este objeto en el montón. Sin embargo, los valores de propiedad se almacenan en la pila.



  const hobbies = ["hiking", "reading"];


Las matrices son objetos, por lo que se almacenan en el montón



  let name = "John";
  const age = 24;

  name = "John Doe";
  const firstName = name.slice(0, 4);


Los primitivos son inmutables. Esto significa que en lugar de cambiar el valor original, JavaScript crea uno nuevo.



Enlaces



Todas las variables se almacenan en la pila. En el caso de valores no primitivos, la pila almacena referencias a un objeto en el montón. La memoria acumulada está desordenada. Por eso necesitamos enlaces en la pila. Puede pensar en los enlaces como direcciones y los objetos como casas en una dirección específica.







En la imagen de arriba, podemos ver cómo se almacenan los distintos valores. Tenga en cuenta que person y newPerson apuntan al mismo objeto



Ejemplos de



  const person = {
    name: "John",
    age: 24,
  };


Esto crea un nuevo objeto en el montón y una referencia a él en la pila.



Recolección de basura



Tan pronto como el motor nota que una variable o función ya no se usa, libera la memoria que ocupa.



De hecho, el problema de liberar la memoria no utilizada no tiene solución: no existe un algoritmo perfecto para resolverlo.



En este artículo, veremos dos algoritmos que ofrecen las mejores soluciones hasta la fecha: recolección de basura de conteo de referencias y marca y barrido.



Recolección de basura mediante recuento de referencias



Aquí todo es simple: objetos para los que no se borran puntos de referencia de la memoria. Veamos un ejemplo. Las líneas representan enlaces.







Tenga en cuenta que solo el objeto "aficiones" permanece en el montón, ya que solo se hace referencia a este objeto en la pila.



Enlaces cíclicos



El problema con este método de recolección de basura es la incapacidad de definir referencias circulares. Esta es una situación en la que dos o más objetos se apuntan entre sí pero no tienen referencias externas. Aquellos. no se puede acceder a estos objetos desde el exterior.



  const son = {
    name: "John",
  };

  const dad = {
    name: "Johnson",
  };

  son.dad = dad;
  dad.son = son;

  son = null;
  dad = null;






Dado que los objetos "hijo" y "papá" se refieren entre sí, el algoritmo de recuento de referencias no podrá liberar memoria. Sin embargo, estos objetos ya no están disponibles para código externo.



Algoritmo para etiquetado y limpieza



Este algoritmo resuelve el problema de las referencias circulares. En lugar de contar las referencias que apuntan a un objeto, determina la disponibilidad del objeto desde el objeto raíz. El objeto raíz es el objeto "ventana" en el navegador o el objeto "global" en Node.js.







El algoritmo marca los objetos como inalcanzables y los elimina. Por tanto, las referencias circulares ya no son un problema. En el ejemplo anterior, los objetos "papá" e "hijo" son inalcanzables desde el objeto raíz. Se marcarán como basura y se eliminarán. El algoritmo en cuestión se ha implementado en todos los navegadores modernos desde 2012. Las mejoras realizadas desde entonces se refieren a mejoras en la implementación y el rendimiento, pero no en la idea central del algoritmo.



Compromisos



La recolección automática de basura nos permite enfocarnos en crear aplicaciones y no perder tiempo en la administración de la memoria. Sin embargo, todo tiene un precio.



Uso de memoria



Dado que los algoritmos necesitan algo de tiempo para determinar que la memoria ya no se usa, las aplicaciones JavaScript tienden a usar más memoria de la que realmente necesitan.



Aunque los objetos están marcados como basura, el recolector debe decidir cuándo recolectarlos para no bloquear el flujo del programa. Si necesita que su aplicación sea lo más eficiente posible en términos de uso de memoria, es mejor que utilice un lenguaje de programación de nivel inferior. Pero tenga en cuenta que estos lenguajes tienen sus propias compensaciones.



Actuación



Los algoritmos de recolección de basura se ejecutan periódicamente para limpiar los objetos no utilizados. El problema es que nosotros, como desarrolladores, no sabemos exactamente cuándo sucederá esto. Grandes cantidades de recolección de basura o recolección frecuente de basura pueden afectar el rendimiento, ya que requiere una cierta cantidad de potencia de procesamiento. Sin embargo, esto suele pasar desapercibido para el usuario y el desarrollador.



Pérdidas de memoria



Echemos un vistazo rápido a los problemas de pérdida de memoria más comunes.



Variables globales



Si declara una variable sin utilizar una de las palabras clave (var, let o const), la variable se convierte en una propiedad del objeto global.



  users = getUsers();


La ejecución de su código en modo estricto evita esto.



A veces declaramos variables globales a propósito. En este caso, para liberar la memoria que ocupa dicha variable, debe asignarle el valor "nulo":



  window.users = null;


Temporizadores y devoluciones de llamada olvidados



Si se olvida de los temporizadores y las devoluciones de llamada, el uso de memoria de su aplicación puede aumentar drásticamente. Tenga cuidado, especialmente al crear aplicaciones de una sola página (SPA) donde los controladores de eventos y las devoluciones de llamada se agregan de forma dinámica.



Temporizadores olvidados



  const object = {};
  const intervalId = setInterval(function () {
    // ,   ,      ,
    //   ,     
    doSomething(object);
  }, 2000);


El código anterior ejecuta la función cada 2 segundos. Si ya no necesita el temporizador, debe cancelarlo:



  clearInterval(intervalId);


Esto es especialmente importante para el SPA. Incluso si va a otra página donde el temporizador no está en uso, se ejecutará en segundo plano.



Devoluciones de llamada olvidadas



Suponga que registra un controlador para un clic de botón que luego elimina. De hecho, esto ya no es un problema, pero aún se recomienda eliminar los controladores que ya no son necesarios:



  const element = document.getElementById("button");
  const onClick = () => alert("hi");

  element.addEventListener("click", onClick);

  element.removeEventListener("click", onClick);
  element.parentNode.removeChild(element);


Enlaces fuera del DOM



Esta fuga de memoria es similar a las anteriores, ocurre al almacenar elementos DOM en JavaScript:



  const elements = [];
  const element = document.getElementById("button");
  elements.push(element);

  function removeAllElements() {
    elements.forEach((item) => {
      document.body.removeChild(document.getElementById(item.id));
    });
  }


Si elimina alguno de estos elementos, también debe eliminarlo de la matriz. De lo contrario, el recolector de basura no puede eliminar dichos elementos:



  const elements = [];
  const element = document.getElementById("button");
  elements.push(element);

  function removeAllElements() {
    elements.forEach((item, index) => {
      document.body.removeChild(document.getElementById(item.id));
      elements.splice(index, 1);
    });
  }


Espero que hayas encontrado algo interesante para ti. Gracias por su atención.



All Articles