Principes de base de la gestion de la mémoire JavaScript: comment cela fonctionne et quels problÚmes peuvent survenir





La plupart des développeurs pensent rarement à la maniÚre dont la gestion de la mémoire JavaScript est implémentée. Le moteur fait généralement tout pour le programmeur, donc cela n'a aucun sens pour ce dernier de réfléchir aux principes du mécanisme de gestion de la mémoire.



Mais tÎt ou tard, les développeurs doivent encore faire face à des problÚmes de mémoire, tels que des fuites. Eh bien, il ne sera possible de les traiter que lorsqu'il y aura une compréhension du mécanisme d'allocation de mémoire. Cet article est consacré aux explications. Il fournit également des conseils sur les types les plus courants de fuites de mémoire dans JavaScript.



Cycle de vie de la mémoire



Lors de la création de fonctions, de variables, etc. en JavaScript, le moteur alloue une certaine quantité de mémoire. Ensuite, il le libÚre - aprÚs que la mémoire n'est plus nécessaire.



En fait, l'allocation de mĂ©moire peut ĂȘtre appelĂ©e le processus de rĂ©servation d'une certaine quantitĂ© de mĂ©moire. Eh bien, sa libĂ©ration est le retour de la rĂ©serve dans le systĂšme. Vous pouvez le rĂ©utiliser autant de fois que vous le souhaitez.



Lorsqu'une variable est déclarée ou qu'une fonction est créée, la mémoire passe par la boucle suivante.







Ici en blocs:



  • Allocate est l'allocation de mĂ©moire effectuĂ©e par le moteur. Il alloue la mĂ©moire requise pour l'objet créé.
  • Utilisation - utilisation de la mĂ©moire. Le dĂ©veloppeur est responsable de ce moment, Ă©crivant dans le code pour lire et Ă©crire en mĂ©moire.
  • LibĂ©rer - libĂ©rer de la mĂ©moire. C'est lĂ  que JavaScript prend de nouveau son envol. Une fois la rĂ©serve libĂ©rĂ©e, la mĂ©moire peut Ă©galement ĂȘtre utilisĂ©e Ă  d'autres fins.


«Objets» dans le contexte de la gestion de la mémoire signifie non seulement des objets JS, mais également des fonctions et des portées.



Pile de mémoire et tas



En gĂ©nĂ©ral, tout semble clair - JavaScript alloue de la mĂ©moire pour tout ce que le dĂ©veloppeur spĂ©cifie dans le code, puis, lorsque toutes les opĂ©rations sont terminĂ©es, la mĂ©moire est libĂ©rĂ©e. Mais oĂč sont stockĂ©es les donnĂ©es?



Il existe deux options - sur la pile de mémoire et sur le tas. Quelle est la premiÚre chose, quelle est la seconde - le nom des structures de données utilisées par le moteur à des fins différentes.



La pile est l'allocation de mémoire statique







La définition de la pile est connue de beaucoup. C'est une structure de données qui est utilisée pour stocker des données statiques, sa taille est toujours connue au moment de la compilation. JS a inclus des valeurs primitives telles que string, number, boolean, undefined et null, ainsi que des références de fonction et d'objet.



Le moteur "comprend" que la taille des données ne change pas, il alloue donc une quantité fixe de mémoire pour chacune des valeurs. Le processus d'allocation de mémoire avant l'exécution est appelé allocation de mémoire statique.



Étant donnĂ© que le navigateur alloue de la mĂ©moire Ă  l'avance pour chaque type de donnĂ©es, il y a une limite Ă  la taille des donnĂ©es qui peuvent y ĂȘtre placĂ©es. Étant donnĂ© que le navigateur alloue de la mĂ©moire Ă  l'avance pour chaque type de donnĂ©es, il y a une limite Ă  la taille des donnĂ©es qui peuvent y ĂȘtre placĂ©es.



Le tas - allocation de mémoire dynamique



Quant au tas, il est aussi familier à beaucoup que la pile. Il est utilisé pour stocker des objets et des fonctions.



Mais contrairement à la pile, le moteur ne peut pas "savoir" combien de mémoire est nécessaire pour un objet particulier, donc la mémoire est allouée selon les besoins. Et cette façon d'allouer de la mémoire est appelée «dynamique» (allocation de mémoire dynamique).



Quelques exemples



Les commentaires sur le code indiquent les nuances d'allocation mémoire.



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





// JavaScript alloue de la mémoire pour cet objet sur le tas.

// Les valeurs elles-mĂȘmes sont primitives, elles sont donc stockĂ©es sur la pile.



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





// Les tableaux sont également des objets, ils vont donc dans le tas.



let name = 'John'; // alloue de la mémoire pour la chaßne

const age = 24; // alloue de la mémoire pour le numéro

name = 'John Doe'; // alloue de la mémoire pour une nouvelle ligne

const firstName = name.slice (0,4); // alloue de la mémoire pour une nouvelle ligne



// Les valeurs primitives sont intrinsĂšquement immuables: au lieu de changer la valeur initiale,

// JavaScript en crée une autre.



Liens JavaScript



Quant à la pile, toutes les variables pointent vers elle. Si la valeur n'est pas primitive, la pile contient une référence à l'objet de tas.



Il n'y a pas d'ordre particulier, ce qui signifie qu'une référence à la zone mémoire souhaitée est stockée sur la pile. Dans une telle situation, l'objet dans le tas ressemble à un bùtiment, mais le lien est son adresse.



JS stocke des objets et des fonctions sur le tas. Mais les valeurs primitives et les références sont sur la pile.







Cette image montre l'organisation du stockage des diffĂ©rentes valeurs. Notez que personne et newPerson pointent vers le mĂȘme objet ici.



Exemple



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





// Un nouvel objet est créé sur le tas et une référence à celui-ci sur la pile.



En gĂ©nĂ©ral, les liens sont extrĂȘmement importants en JavaScript.



Collecte des ordures



Il est maintenant temps de revenir au cycle de vie de la mémoire, à savoir sa libération.



Le moteur JavaScript est responsable non seulement de l'allocation de mémoire, mais également de la désallocation. Dans ce cas, le garbage collector renvoie la mémoire au systÚme.



Et dÚs que le moteur "voit" que la variable ou la fonction n'est plus nécessaire, la mémoire est libérée.



Mais il y a un problÚme clé ici. Le fait est qu'il est impossible de décider si une certaine zone de mémoire est nécessaire ou non. Il n'y a pas d'algorithme aussi précis que de libérer de la mémoire en temps réel.



Certes, il n'y a que des algorithmes qui fonctionnent bien qui vous permettent de le faire. Ils ne sont pas parfaits, mais toujours bien meilleurs que beaucoup d'autres. Ci-dessous - une histoire sur le garbage collection, qui est basé sur le comptage de références, ainsi que sur l '"algorithme de marquage".



Et les liens?



C'est un algorithme trÚs simple. Il supprime les objets auxquels aucun autre point de référence. Voici un exemple qui l'explique assez bien.



Si vous avez regardé la vidéo, vous avez probablement remarqué que les loisirs sont le seul objet du tas qui a été référencé sur la pile.



Cycles



L'inconvénient de l'algorithme est qu'il ne peut pas prendre en compte les références circulaires. Ils surviennent lorsqu'un ou plusieurs objets se réfÚrent les uns aux autres, hors de portée du point de vue du code.



let son = {
  name: 'John',
};
let dad = {
  name: 'Johnson',
}
 
son.dad = dad;
dad.son = son;
son = null;
dad = null;
      
      







Ici, le fils et le pÚre se réfÚrent. Il n'y a pas d'accÚs aux objets pendant une longue période, mais l'algorithme ne libÚre pas la mémoire qui leur est allouée.



Précisément parce que l'algorithme compte les références, l'affectation de null aux objets ne fait rien, car chaque objet a toujours une référence.



Algorithme pour les annotations



C'est là qu'un autre algorithme vient à la rescousse, appelé la méthode de marquage et de balayage. Cet algorithme ne compte pas les références, mais détermine si différents objets sont accessibles via l'objet racine. Dans le navigateur, c'est window, et dans Node.js, c'est global.







Si l'objet n'est pas disponible, l'algorithme le marque puis le supprime. Dans ce cas, les objets racine ne sont jamais dĂ©truits. Le problĂšme des rĂ©fĂ©rences cycliques n'est pas pertinent ici - l'algorithme nous permet de comprendre que ni papa ni fils ne sont dĂ©jĂ  inaccessibles, donc ils peuvent ĂȘtre «balayĂ©s» et la mĂ©moire renvoyĂ©e au systĂšme.



Depuis 2012, absolument tous les navigateurs sont équipés de poubelles qui fonctionnent exactement selon la méthode de marquage et de balayage.



Non sans ses inconvénients ici.





On pourrait penser que tout va bien, et maintenant vous pouvez oublier la gestion de la mĂ©moire, laissant tout Ă  l'algorithme. Mais ce n’est pas le cas.



Utilisation importante de la mémoire



Étant donnĂ© que les algorithmes ne savent pas quand la mĂ©moire n'est plus nĂ©cessaire, les applications JavaScript peuvent utiliser plus de mĂ©moire que nĂ©cessaire. Et seul le collecteur peut dĂ©cider de libĂ©rer ou non la mĂ©moire allouĂ©e.



JavaScript est meilleur pour gérer la mémoire dans les langages de bas niveau. Mais il y a aussi des inconvénients ici, qu'il faut garder à l'esprit. En particulier, JS ne fournit pas d'outils de gestion de la mémoire, contrairement aux langages de bas niveau, dans lesquels le programmeur s'occupe "manuellement" de l'allocation et de la libération de la mémoire.



Performance



La mémoire n'est pas effacée à chaque nouveau moment dans le temps. La libération est effectuée à intervalles réguliers. Mais les développeurs ne peuvent pas savoir exactement quand ces processus sont lancés.



Par conséquent, dans certains cas, le garbage collection peut avoir un impact négatif sur les performances, car l'algorithme a besoin de certaines ressources pour fonctionner. Certes, la situation devient rarement complÚtement ingérable. Le plus souvent, les conséquences sont microscopiques.



Fuites de mémoire



Les fuites de mémoire sont l'une des choses les plus frustrantes du développement. Mais si vous connaissez tous les types de fuites les plus courants, vous pouvez contourner le problÚme sans trop de difficulté.



Variables globales Les



fuites de mémoire se produisent le plus souvent en raison du stockage de données dans des variables globales.



Dans le navigateur, si vous faites une erreur et utilisez var au lieu de const ou let, le moteur attachera la variable Ă  l'objet window. De mĂȘme, il effectuera l'opĂ©ration sur les fonctions dĂ©finies par le mot fonction.



user = getUser();
var secondUser = getUser();
function getUser() {
  return 'user';
}
      
      





// Les trois variables - user, secondUser et

// getUser - seront attachées à l'objet window.



Cela ne peut ĂȘtre fait qu'avec des fonctions et des variables dĂ©clarĂ©es dans la portĂ©e globale. Vous pouvez contourner ce problĂšme en exĂ©cutant votre code en mode strict.



Les variables globales sont souvent déclarées intentionnellement, ce n'est pas toujours une erreur. MAIS, dans tous les cas, il ne faut pas oublier de libérer de la mémoire une fois que les données ne sont plus nécessaires. Pour ce faire, vous devez affecter null à la variable globale.



window.users = null;



Rappels et minuteries



L'application utilise plus de mĂ©moire qu'elle ne le devrait, mĂȘme si nous oublions les minuteries et les rappels. Le principal problĂšme concerne les applications Ă  page unique (SPA), ainsi que l'ajout dynamique de rappels et de gestionnaires d'Ă©vĂ©nements.



Minuteries oubliées



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





Cette fonction s'exĂ©cute toutes les deux secondes. Sa mise en Ɠuvre ne doit pas ĂȘtre sans fin. Le problĂšme est que les objets qui ont une rĂ©fĂ©rence dans l'intervalle ne sont pas dĂ©truits tant que l'intervalle n'est pas effacĂ©. Par consĂ©quent, vous devez prescrire en temps opportun:

clearInterval (intervalId); Rappel



oublié Un



problĂšme peut survenir si un gestionnaire onClick est attachĂ© Ă  un bouton et que le bouton lui-mĂȘme est supprimĂ© aprĂšs - par exemple, il n'est plus nĂ©cessaire.



Auparavant, la plupart des navigateurs ne pouvaient tout simplement pas libĂ©rer la mĂ©moire allouĂ©e Ă  un tel gestionnaire d'Ă©vĂ©nements. Maintenant, ce problĂšme appartient au passĂ©, mais quand mĂȘme, laisser les gestionnaires dont vous n'avez plus besoin n'en vaut pas la peine.



const element = document.getElementById('button');
const onClick = () => alert('hi');
element.addEventListener('click', onClick);
element.removeEventListener('click', onClick);
element.parentNode.removeChild(element);
      
      







ÉlĂ©ments DOM oubliĂ©s dans les variables



Ceci est similaire au cas précédent. L'erreur se produit lorsque les éléments DOM sont stockés dans une variable.



const elements = [];
const element = document.getElementById('button');
elements.push(element);
function removeAllElements() {
  elements.forEach((item) => {
document.body.removeChild(document.getElementById(item.id))
  });
}
      
      





Lorsque vous supprimez l'un des éléments ci-dessus, vous devez également prendre soin de le supprimer du tableau. Sinon, le garbage collector ne le supprimera pas automatiquement.



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);
 });
}
      
      





En supprimant un élément du tableau, vous mettez à jour son contenu avec la liste des éléments de la page.



Étant donnĂ© que chaque Ă©lĂ©ment de la maison a une rĂ©fĂ©rence Ă  son parent, cela empĂȘche de maniĂšre rĂ©fĂ©rentielle le ramasse-miettes de libĂ©rer la mĂ©moire occupĂ©e par le parent, ce qui entraĂźne des fuites.



Dans le résidu sec



L'article décrit la mécanique générale de l'allocation de mémoire, ainsi que l'auteur a montré quels problÚmes peuvent survenir et comment les résoudre. Tout cela est important pour tout développeur Java Script.



, Frontend- Skillbox:



, - ( SPA — Single Page Applications).



, “ ” — ( , ), , , .



— . .



, ( , , ). , , “” — garbage collector.



- (js , garbage collector’a). , .



All Articles