Un article pour comprendre les fuites de mémoire JavaScript, les mécanismes de garbage collection

你越是认真生活,你的生活就会越美好 —— Frank Lloyd Wright (documentaire sur les fruits de la vie)

勤学如春起之苗,不见其增,日有所长;
辍学如磨刀之石,不见其损,日有所亏。 —— Tao Yuanming

1. Qu'est-ce qu'une fuite de mémoire?

Le fonctionnement du programme est requis 内存. Tant que le programme l'exige, le système d'exploitation ou le runtime (runtime) doit être fourni 内存.

Pour un fonctionnement continu 服务进程(daemon), un must 及时释放不再用到的内存. Sinon, l'utilisation de la mémoire deviendra de plus en plus élevée, ce qui affectera les performances du système ou provoquera le blocage du processus.

Insérez la description de l'image ici

不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)

Certains langages (comme le langage C) doivent libérer la mémoire manuellement et le programmeur est responsable de la gestion de la mémoire.

char * buffer;
buffer = (char*) malloc(42);

// Do something with buffer

free(buffer);

Ce qui précède est le C 语言code, malloc方法utilisé pour demander de la mémoire, après l'avoir utilisé, vous devez free方法libérer la mémoire par vous-même .

Ceci est très gênant, donc la plupart des langages fournissent une gestion automatique de la mémoire pour réduire le fardeau du programmeur. Cela s'appelle " 垃圾回收机制" ( garbage collector).

Deux, mécanisme de ramassage des ordures

Comment le mécanisme de récupération de place sait-il quelle mémoire n'est plus nécessaire?

La méthode la plus couramment utilisée est appelée " 引用计数" (comptage de références): le moteur de langage dispose d'une "table de référence" qui enregistre toutes les ressources (généralement diverses valeurs) dans la mémoire 引用次数. Si le nombre de références à une valeur est 0, cela signifie que la valeur n'est plus utilisée, donc cette mémoire peut être libérée.
Insérez la description de l'image ici

Dans la figure ci-dessus, les deux valeurs dans le coin inférieur gauche n'ont pas de références, elles peuvent donc être libérées.

Si une valeur n'est plus nécessaire, mais que le nombre de références n'est pas 0, le mécanisme de garbage collection ne peut pas libérer cette mémoire, ce qui en résultera 内存泄漏.

const arr = [1, 2, 3, 4];
console.log('hello world');

Dans le code ci-dessus, le tableau [1, 2, 3, 4]est une valeur et prendra de la mémoire. La variable arr est la seule référence à cette valeur, elle 引用次数vaut donc 1. Bien que le code suivant n'utilise pas arr, il le fait toujours 持续占用内存.

Si vous ajoutez une ligne de code, 解除arr对[1, 2, 3, 4]引用cette mémoire peut être 垃圾回收机制libérée.

let arr = [1, 2, 3, 4];
console.log('hello world');
arr = null;

Dans le code ci-dessus arr重置为null, la [1, 2, 3, 4]référence à la paire est supprimée , le nombre de références devient 0 et la mémoire peut être libérée.

Par conséquent, cela ne signifie pas que les 垃圾回收机制programmeurs seront soulagés s'ils l'ont fait. Il faut tout de même faire attention à l'encombrement mémoire: une fois que les valeurs qui occupent de l'espace ne sont plus utilisées, il faut vérifier s'il y a encore des références à celles-ci. Si tel est le cas, vous devez déréférencer manuellement.

Troisièmement, la méthode d'identification des fuites de mémoire

Comment l'observer 内存泄漏?

La règle de base est que si l' 连续五次垃圾回收之后utilisation de la mémoire est supérieure à une fois, il y en aura 内存泄漏. Cela nécessite une visualisation en temps réel de l'utilisation de la mémoire.

Le navigateur

ChromeAffichez dans le navigateur 内存占用et suivez les étapes ci-dessous.
Insérez la description de l'image ici
PS: l'image ci-dessous est le système de fenêtre
Insérez la description de l'image ici

  1. Ouvrir 开发者工具, sélectionner le Timelinepanneau
  2. Vérifiez dans le champ Capture en hautMemory
  3. Cliquez sur le bouton d'enregistrement dans le coin supérieur gauche.
  4. Effectuez diverses opérations sur la page pour simuler l'utilisation de l'utilisateur.
  5. Après un certain temps, cliquez sur le bouton d'arrêt dans la boîte de dialogue et la 这段时间的内存占用situation sera affichée sur le panneau .

Si l'utilisation de la mémoire est globalement stable et proche du niveau, cela signifie qu'il n'y a pas de fuite de mémoire.
Insérez la description de l'image ici
Au contraire, c'est une fuite de mémoire.
Insérez la description de l'image ici

Ligne de commande

La ligne de commande peut utiliser la méthode Nodefournie process.memoryUsage.

process.memoryUsage()

Insérez la description de l'image ici

process.memoryUsageRenvoie un objet qui contient Node 进程的内存占用信息. L'objet contient quatre champs, l'unité est l'octet, la signification est la suivante.
Insérez la description de l'image ici

  • rss(resident set size): Toute l'utilisation de la mémoire, y compris la zone d'instructions et la pile.
  • heapTotal: Mémoire occupée par "tas", y compris utilisée et non utilisée.
  • heapUsed: La partie du tas utilisé.
  • external: La mémoire occupée par les objets C ++ à l'intérieur du moteur V8.

判断内存泄漏,以heapUsed字段为准。

Quatre, WeakMap

Comme mentionné précédemment, il est très important de clarifier les références à temps. Cependant, vous ne vous souvenez pas de tant de choses, et parfois vous l'oubliez lorsque vous le négligez, il y a donc tellement de fuites de mémoire.

Il est préférable d'avoir un moyen de déclarer lors de la création d'une nouvelle référence, quelles références doivent être effacées manuellement et quelles références peuvent être ignorées. Lorsque d'autres références disparaissent, le mécanisme de garbage collection peut libérer de la mémoire. Cela peut considérablement réduire la charge du programmeur, tant que vous effacez la référence principale.

ES6 prend cela en considération et introduit deux types 新的数据结构: WeakSetet WeakMap. Leurs références aux valeurs ne sont pas comptées dans le mécanisme de garbage collection, il y aura donc un «faible» dans le nom pour indiquer que c'est le cas 弱引用.

Ci WeakMap- dessous , par exemple, pour voir comment cela a été résolu 内存泄漏dans le.

const wm = new WeakMap();

const element = document.getElementById('example');

wm.set(element, 'some information');
wm.get(element) // "some information"

Insérez la description de l'image ici

Dans le code ci-dessus, créez d'abord un nouveau Weakmap 实例. Ensuite, un DOM 节点comme 键名dépôt à cet exemple, et comme quelques informations supplémentaires 键值, stockés ensemble à l' WeakMapintérieur. À ce stade, la référence à l' WeakMapintérieur elementest une référence faible et ne sera pas comptée dans le mécanisme de récupération de place.

En d'autres termes, le nombre de références de l'objet nœud DOM est 1, pas 2. À ce stade, une fois la référence au nœud éliminée, la mémoire qu'il occupe sera libérée par le mécanisme de garbage collection. La paire clé-valeur enregistrée par Weakmap disparaîtra également automatiquement.

Fondamentalement, si vous souhaitez ajouter des données à l'objet sans interférer avec le mécanisme de garbage collection, vous pouvez l'utiliser WeakMap.

Exemple WeakMap

WeakMapL'exemple est difficile à démontrer, car il est impossible d'observer la référence à l'intérieur, il disparaîtra automatiquement. À ce stade, toutes les autres références sont supprimées et il n'y a plus WeakMaple nom de clé pointé par la référence , ce qui rend impossible de vérifier si le nom de clé existe.

Je ne pouvais pas penser à un moyen jusqu'à ce qu'un jour on me 贺师俊老师dise que si la valeur pointée par la référence occupe beaucoup de mémoire, elle peut être vue à travers la process.memoryUsageméthode.

Sur la base de cette idée, 网友 vtxfl'exemple suivant est ajouté.
Tout d'abord, ouvrez-le Node 命令行.

node --expose-gc

Dans le code ci-dessus, cela --expose-gc参数signifie qu'il est autorisé 手动执行垃圾回收机制.

Ensuite, exécutez le code suivant.

// 手动执行一次垃圾回收,保证获取的内存使用状态准确
> global.gc(); 
undefined

// 查看内存占用的初始状态,heapUsed 为 4M 左右
> process.memoryUsage(); 
{
    
     rss: 21106688,
  heapTotal: 7376896,
  heapUsed: 4153936,
  external: 9059 }

> let wm = new WeakMap();
undefined

> let b = new Object();
undefined

> global.gc();
undefined

// 此时,heapUsed 仍然为 4M 左右
> process.memoryUsage(); 
{
    
     rss: 20537344,
  heapTotal: 9474048,
  heapUsed: 3967272,
  external: 8993 }

// 在 WeakMap 中添加一个键值对,
// 键名为对象 b,键值为一个 5*1024*1024 的数组  
> wm.set(b, new Array(5*1024*1024));
WeakMap {
    
    }

// 手动执行一次垃圾回收
> global.gc();
undefined

// 此时,heapUsed 为 45M 左右
> process.memoryUsage(); 
{
    
     rss: 62652416,
  heapTotal: 51437568,
  heapUsed: 45911664,
  external: 8951 }

// 解除对象 b 的引用  
> b = null;
null

// 再次执行垃圾回收
> global.gc();
undefined

// 解除 b 的引用以后,heapUsed 变回 4M 左右
// 说明 WeakMap 中的那个长度为 5*1024*1024 的数组被销毁了
> process.memoryUsage(); 
{
    
     rss: 20639744,
  heapTotal: 8425472,
  heapUsed: 3979792,
  external: 8956 }

Insérez la description de l'image ici

Dans le code ci-dessus, tant que la référence externe disparaît, la référence interne de WeakMap sera automatiquement effacée par le garbage collection. Cela montre qu'avec son aide, la résolution des fuites de mémoire sera beaucoup plus simple.


谢谢你阅读到了最后~
期待你关注、收藏、评论、点赞~
让我们一起 变得更强


Lien original
Tutoriel JavaScript sur les fuites de mémoire - Enseignant Ruan Yifeng

Il est recommandé de lire la
portée JavaScript et la promotion des variables
Apprentissage du code source Vue

Je suppose que tu aimes

Origine blog.csdn.net/weixin_42752574/article/details/111088329
conseillé
Classement