Maison  >  Article  >  interface Web  >  Collecte des déchets et fuites de mémoire en JavaScript

Collecte des déchets et fuites de mémoire en JavaScript

藏色散人
藏色散人avant
2019-05-06 09:33:292383parcourir

Avant-propos

L'exécution du programme nécessite de la mémoire. Chaque fois qu'un programme le demande, le système d'exploitation ou le runtime doit fournir de la mémoire. Ce qu'on appelle une fuite de mémoire est simplement une mémoire qui n'est plus utilisée et qui n'est pas libérée à temps. Afin de mieux éviter les fuites de mémoire, nous introduisons d'abord le mécanisme de garbage collection Javascript.

Dans des langages tels que C et C++, les développeurs peuvent contrôler directement l'application et le recyclage de la mémoire. Cependant, dans les langages Java, C# et JavaScript, l'application et la libération de l'espace mémoire pour les variables sont gérées par le programme lui-même et les développeurs n'ont pas besoin de s'en soucier. En d’autres termes, Javascript dispose d’un mécanisme automatique de garbage collection (Garbage Collection).

1. La nécessité du ramassage des ordures

Le passage suivant est extrait de « Le guide définitif de JavaScript (quatrième édition) »

En raison de string Les objets et les tableaux n'ont pas de tailles fixes, ils peuvent donc être alloués dynamiquement lorsque leurs tailles sont connues. Chaque fois qu'un programme JavaScript crée une chaîne, un tableau ou un objet, l'interpréteur doit allouer de la mémoire pour stocker cette entité. Chaque fois que vous allouez dynamiquement de la mémoire de cette manière, elle doit éventuellement être libérée pour pouvoir être réutilisée. Sinon, l'interpréteur JavaScript consommera toute la mémoire disponible dans le système, provoquant un crash du système.

Collecte des déchets et fuites de mémoire en JavaScript

Ce passage explique pourquoi le système a besoin d'un garbage collection. JavaScript n'est pas comme C/C++, il a son propre mécanisme de garbage collection.

Le mécanisme du garbage collection JavaScript est très simple : rechercher les variables qui ne sont plus utilisées, puis libérer la mémoire qu'elles occupent. Cependant, ce processus ne prend pas de temps car sa surcharge est relativement importante, donc. le ramasse-miettes suivra un programme fixe exécuté périodiquement à intervalles réguliers.

var a = "浪里行舟";
var b = "前端工匠";
var a = b; //重写a

Après l'exécution de ce code, la chaîne "Wanglizhou" perd sa référence (elle était auparavant référencée par a). Une fois que le système aura détecté ce fait, il libérera l'espace de stockage de la chaîne afin que ces espaces soient). peut être réutilisé.

2. Mécanisme de récupération de place

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

Il existe deux méthodes de collecte des déchets : l'effacement des marques et le comptage des références. Le comptage de références est moins couramment utilisé, le marquage et le balayage sont plus couramment utilisés.

1. Effacement des marques

Il s'agit de la méthode de récupération de place la plus couramment utilisée en javascript. Lorsqu'une variable entre dans l'environnement d'exécution, marquez la variable comme "entrant dans l'environnement". Logiquement, la mémoire occupée par les variables entrant dans l'environnement ne pourra jamais être libérée, car elles pourront être utilisées à chaque fois que le flux d'exécution entre dans l'environnement correspondant. Lorsqu'une variable quitte l'environnement, elle est marquée comme "quittant l'environnement".

Lorsque le garbage collector s'exécute, il marquera toutes les variables stockées en mémoire. Ensuite, il supprime les variables de l'environnement et les balises référencées par les variables de l'environnement. Les variables marquées après cela seront considérées comme des variables à supprimer car les variables de l'environnement ne peuvent plus accéder à ces variables. enfin. Le garbage collector termine le travail de nettoyage de la mémoire, détruit les valeurs marquées et récupère l'espace mémoire qu'elles occupent.

Collecte des déchets et fuites de mémoire en JavaScript

Utilisons un exemple pour expliquer cette méthode :

var m = 0,n = 19 // 把 m,n,add() 标记为进入环境。
add(m, n) // 把 a, b, c标记为进入环境。
console.log(n) // a,b,c标记为离开环境,等待垃圾回收。
function add(a, b) {
  a++
  var c = a + b
  return c
}

2. Comptage de références

Le so- appelé " "Comptage de références" signifie que le moteur de langage dispose d'une "table de référence" qui enregistre le nombre de références à toutes les ressources (généralement diverses valeurs) dans la mémoire. Si le compteur de référence d'une valeur est 0, cela signifie que la valeur n'est plus utilisée, la mémoire peut donc être libérée.

Collecte des déchets et fuites de mémoire en JavaScript

Dans l'image ci-dessus, les deux valeurs dans le coin inférieur gauche n'ont aucune référence, elles peuvent donc être publiées.

Si une valeur n'est plus nécessaire mais que le numéro de référence n'est pas 0, le mécanisme de garbage collection ne peut pas libérer cette mémoire, ce qui entraîne une fuite de mémoire.

var arr = [1, 2, 3, 4];
arr = [2, 4, 5]
console.log('浪里行舟');

Dans le code ci-dessus, le tableau [1, 2, 3, 4] est une valeur et occupera de la mémoire. La variable arr est la seule référence à cette valeur, le nombre de références est donc 1. Bien que arr ne soit pas utilisé dans le code suivant, il continuera à occuper de la mémoire. Quant à la façon de libérer de la mémoire, nous la présenterons ci-dessous.

Dans la troisième ligne de code, la variable arr référencée par le tableau [1, 2, 3, 4] a obtenu une autre valeur, alors le nombre de références au tableau [1, 2, 3, 4] est réduit de 1 , à ce moment son compte de référence devient 0, ce qui signifie qu'il n'y a plus moyen d'accéder à cette valeur, donc l'espace mémoire qu'il occupe peut être récupéré.

Mais le plus gros problème avec le comptage de références est : référence circulaire

function func() {
    let obj1 = {};
    let obj2 = {};
    obj1.a = obj2; // obj1 引用 obj2
    obj2.a = obj1; // obj2 引用 obj1
}

Lorsque la fonction func est exécutée, la valeur de retour n'est pas définie, donc toute la fonction et les variables internes doivent être recyclées, mais selon Selon la méthode de comptage de références, le nombre de références de obj1 et obj2 n'est pas 0, elles ne seront donc pas recyclées.

Pour résoudre le problème des références circulaires, il est préférable de les définir manuellement sur null lorsqu'elles ne sont pas utilisées. L'exemple ci-dessus peut être réalisé comme ceci :

obj1 = null;
obj2 = null;

3. Quelles situations peuvent provoquer des fuites de mémoire ?

虽然JavaScript会自动垃圾收集,但是如果我们的代码写法不当,会让变量一直处于“进入环境”的状态,无法被回收。下面列一下内存泄漏常见的几种情况:

1. 意外的全局变量

function foo(arg) {
    bar = "this is a hidden global variable";
}

bar没被声明,会变成一个全局变量,在页面关闭之前不会被释放。

另一种意外的全局变量可能由 this 创建:

function foo() {
    this.variable = "potential accidental global";
}
// foo 调用自己,this 指向了全局对象(window)
foo();

在 JavaScript 文件头部加上 'use strict',可以避免此类错误发生。启用严格模式解析 JavaScript ,避免意外的全局变量。

2. 被遗忘的计时器或回调函数

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        // 处理 node 和 someResource
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);

这样的代码很常见,如果id为Node的元素从DOM中移除,该定时器仍会存在,同时,因为回调函数中包含对someResource的引用,定时器外面的someResource也不会被释放。

3. 闭包

function bindEvent(){
  var obj=document.createElement('xxx')
  obj.onclick=function(){
    // Even if it is a empty function
  }
}

闭包可以维持函数内局部变量,使其得不到释放。上例定义事件回调时,由于是函数内定义函数,并且内部函数--事件回调引用外部函数,形成了闭包。

// 将事件处理函数定义在外面
function bindEvent() {
  var obj = document.createElement('xxx')
  obj.onclick = onclickHandler
}
// 或者在定义事件处理函数的外部函数中,删除对dom的引用
function bindEvent() {
  var obj = document.createElement('xxx')
  obj.onclick = function() {
    // Even if it is a empty function
  }
  obj = null
}

解决之道,将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对dom的引用。

4. 没有清理的DOM元素引用

有时,保存 DOM 节点内部数据结构很有用。假如你想快速更新表格的几行内容,把每一行 DOM 存成字典(JSON 键值对)或者数组很有意义。此时,同样的 DOM 元素存在两个引用:一个在 DOM 树中,另一个在字典中。将来你决定删除这些行时,需要把两个引用都清除。

var elements = {
    button: document.getElementById('button'),
    image: document.getElementById('image'),
    text: document.getElementById('text')
};
function doStuff() {
    image.src = 'http://some.url/image';
    button.click();
    console.log(text.innerHTML);
}
function removeButton() {
    document.body.removeChild(document.getElementById('button'));
    // 此时,仍旧存在一个全局的 #button 的引用
    // elements 字典。button 元素仍旧在内存中,不能被 GC 回收。
}

虽然我们用removeChild移除了button,但是还在elements对象里保存着#button的引用,换言之,DOM元素还在内存里面。

四、内存泄漏的识别方法

新版本的chrome在 performance 中查看:

Collecte des déchets et fuites de mémoire en JavaScript

Collecte des déchets et fuites de mémoire en JavaScript

步骤:

● 打开开发者工具 Performance

● 勾选 Screenshots 和 memory

● 左上角小圆点开始录制(record)

● 停止录制

图中 Heap 对应的部分就可以看到内存在周期性的回落也可以看到垃圾回收的周期,如果垃圾回收之后的最低值(我们称为min),min在不断上涨,那么肯定是有较为严重的内存泄漏问题。

避免内存泄漏的一些方式:

减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收

注意程序逻辑,避免“死循环”之类的

避免创建过多的对象

总而言之需要遵循一条原则:不用了的东西要及时归还

五、垃圾回收的使用场景优化

1. 数组array优化

将[]赋值给一个数组对象,是清空数组的捷径(例如: arr = [];),但是需要注意的是,这种方式又创建了一个新的空对象,并且将原来的数组对象变成了一小片内存垃圾!实际上,将数组长度赋值为0(arr.length = 0)也能达到清空数组的目的,并且同时能实现数组重用,减少内存垃圾的产生。

const arr = [1, 2, 3, 4];
console.log('浪里行舟');
arr.length = 0  // 可以直接让数字清空,而且数组类型不变。
// arr = []; 虽然让a变量成一个空数组,但是在堆上重新申请了一个空数组对象。

2. 对象尽量复用

对象尽量复用,尤其是在循环等地方出现创建新对象,能复用就复用。不用的对象,尽可能设置为null,尽快被垃圾回收掉。

var t = {} // 每次循环都会创建一个新对象。
for (var i = 0; i < 10; i++) {
  // var t = {};// 每次循环都会创建一个新对象。
  t.age = 19
  t.name = &#39;123&#39;
  t.index = i
  console.log(t)
}
t = null //对象如果已经不用了,那就立即设置为null;等待垃圾回收。

3. 在循环中的函数表达式,能复用最好放到循环外面。

// 在循环中最好也别使用函数表达式。
for (var k = 0; k < 10; k++) {
  var t = function(a) {
    // 创建了10次  函数对象。
    console.log(a)
  }
  t(k)
}
// 推荐用法
function t(a) {
  console.log(a)
}
for (var k = 0; k < 10; k++) {
  t(k)
}
t = null

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer
Article précédent:Comment écrire ajax en jsArticle suivant:Comment écrire ajax en js