Heim >Web-Frontend >js-Tutorial >Detaillierte Erklärung der Speicherbereinigung und Speicherlecks in JS

Detaillierte Erklärung der Speicherbereinigung und Speicherlecks in JS

青灯夜游
青灯夜游nach vorne
2020-11-13 17:56:162212Durchsuche

Detaillierte Erklärung der Speicherbereinigung und Speicherlecks in JS

Das Programm benötigt Speicher zum Ausführen. Immer wenn ein Programm danach fragt, muss das Betriebssystem oder die Laufzeit Speicher bereitstellen. Beim sogenannten Memory Leak handelt es sich einfach um Speicher, der nicht mehr genutzt wird und nicht rechtzeitig freigegeben wird. Um Speicherlecks besser zu vermeiden, führen wir zunächst den Javascript-Garbage-Collection-Mechanismus ein.

In Sprachen wie C und C++ können Entwickler die Anwendung und Wiederverwendung von Speicher direkt steuern. In den Sprachen Java, C# und JavaScript erfolgt die Beantragung und Freigabe von Speicherplatz für Variablen jedoch vom Programm selbst, und Entwickler müssen sich nicht darum kümmern. Mit anderen Worten, Javascript verfügt über einen automatischen Garbage-Collection-Mechanismus (Garbage Collection).

1. Die Notwendigkeit der Garbage Collection

Die folgende Passage stammt aus „The Definitive Guide to JavaScript (Fourth Edition)“

Da Strings, Objekte und Arrays keine feste Größe haben, können sie nur verwendet werden, wenn ihre Größe festgelegt ist sind bekannt. Weisen Sie ihnen dynamisch Speicher zu. Jedes Mal, wenn ein JavaScript-Programm eine Zeichenfolge, ein Array oder ein Objekt erstellt, muss der Interpreter Speicher zum Speichern dieser Entität zuweisen. Wenn Sie auf diese Weise dynamisch Speicher zuweisen, muss dieser irgendwann freigegeben werden, damit er wiederverwendet werden kann. Andernfalls verbraucht der JavaScript-Interpreter den gesamten verfügbaren Speicher im System, was zum Absturz des Systems führt.

Detaillierte Erklärung der Speicherbereinigung und Speicherlecks in JS

Diese Passage erklärt, warum das System keine Garbage Collection benötigt. JavaScript verfügt über einen eigenen Garbage Collection-Mechanismus.

Der Mechanismus der JavaScript-Garbage Collection ist sehr einfach: Finden Sie die Variablen, die nicht mehr verwendet werden, und geben Sie dann den von ihnen belegten Speicher frei. Dieser Vorgang ist jedoch nicht zeitaufwändig, da er relativ teuer ist, sodass der Garbage Collector einen Zyklus durchführt in einem festen Zeitintervall.

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

Nachdem dieser Code ausgeführt wurde, verliert die Zeichenfolge „Wanglizhou“ ihre Referenz (sie wurde zuvor von a referenziert). Nachdem das System diese Tatsache erkannt hat, gibt es den Speicherplatz der Zeichenfolge frei, sodass der Speicherplatz wiederverwendet werden kann .

2. Garbage-Collection-Mechanismus

Woher weiß der Garbage-Collection-Mechanismus, welcher Speicher nicht mehr benötigt wird?

Es gibt zwei Methoden der Speicherbereinigung: Markierungslöschung und Referenzzählung. Referenzzählung wird seltener verwendet, Mark-and-Sweep wird häufiger verwendet.

1. Markierung löschen

Dies ist die am häufigsten verwendete Garbage-Collection-Methode in Javascript. Wenn eine Variable in die Ausführungsumgebung eintritt, markieren Sie die Variable als „in die Umgebung eintreten“. Logischerweise kann der Speicher, der von Variablen belegt wird, die in die Umgebung gelangen, niemals freigegeben werden, da sie immer dann verwendet werden können, wenn der Ausführungsfluss in die entsprechende Umgebung eintritt. Wenn eine Variable die Umgebung verlässt, wird sie als „die Umgebung verlassen“ markiert.

Der Garbage Collector markiert bei seiner Ausführung alle im Speicher gespeicherten Variablen. Anschließend werden die Variablen in der Umgebung und die Tags entfernt, auf die die Variablen in der Umgebung verweisen. Variablen, die danach markiert werden, werden als zu löschende Variablen betrachtet, da Variablen in der Umgebung nicht mehr auf diese Variablen zugreifen können. endlich. Der Garbage Collector führt die Speicherbereinigung durch, zerstört die markierten Werte und gewinnt den von ihnen belegten Speicherplatz zurück.

Detaillierte Erklärung der Speicherbereinigung und Speicherlecks in JS

Lassen Sie uns diese Methode anhand eines Beispiels erklären:

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. Referenzzählung

Die sogenannte „Referenzzählung“ bedeutet, dass die Sprach-Engine über eine „Referenztabelle“ verfügt, die alle Ressourcen im Speicher speichert (normalerweise verschiedene Werte). Wenn die Anzahl der Verweise auf einen Wert 0 beträgt, bedeutet dies, dass der Wert nicht mehr verwendet wird und der Speicher freigegeben werden kann.

Detaillierte Erklärung der Speicherbereinigung und Speicherlecks in JS

Im obigen Bild haben die beiden Werte in der unteren linken Ecke keine Referenzen und können daher freigegeben werden.

Wenn ein Wert nicht mehr benötigt wird, die Referenznummer jedoch nicht 0 ist, kann der Garbage-Collection-Mechanismus diesen Speicher nicht freigeben, was zu einem Speicherverlust führt.

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

Im obigen Code ist das Array [1, 2, 3, 4] ein Wert und belegt Speicher. Die Variable arr ist die einzige Referenz auf diesen Wert, daher beträgt die Anzahl der Referenzen 1. Obwohl arr im folgenden Code nicht verwendet wird, belegt es weiterhin Speicher. Die Vorgehensweise zur Speicherfreigabe wird im Folgenden vorgestellt.

In der dritten Codezeile hat die Variable arr, auf die das Array [1, 2, 3, 4] verweist, einen anderen Wert erhalten, sodass die Anzahl der Verweise auf das Array [1, 2, 3, 4] um reduziert wird 1. Zu diesem Zeitpunkt ist es Wenn die Anzahl der Referenzen 0 wird, bedeutet dies, dass es keine Möglichkeit mehr gibt, auf diesen Wert zuzugreifen, sodass der von ihm belegte Speicherplatz wiederhergestellt werden kann.

Aber die Referenzzählung hat das größte Problem: Zirkelverweis

function func() {
    let obj1 = {};
    let obj2 = {};

    obj1.a = obj2; // obj1 引用 obj2
    obj2.a = obj1; // obj2 引用 obj1
}

Wenn die Funktion func endet, ist der Rückgabewert undefiniert, daher sollten die gesamte Funktion und die internen Variablen recycelt werden, aber gemäß der Referenzzählmethode obj1 und obj2 Die Die Anzahl der Referenzen ist nicht 0 und wird daher nicht recycelt.

Um das Problem der Zirkelverweise zu lösen, ist es am besten, sie manuell auf leer zu setzen, wenn sie nicht verwendet werden. Das obige Beispiel kann wie folgt umgesetzt werden:

obj1 = null;
obj2 = null;

三、哪些情况会引起内存泄漏?

虽然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 中查看:

Detaillierte Erklärung der Speicherbereinigung und Speicherlecks in JS

步骤:

  • 打开开发者工具 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

参考资料

作者:浪里行舟

更多编程相关知识,请访问:编程学习网站!!

Das obige ist der detaillierte Inhalt vonDetaillierte Erklärung der Speicherbereinigung und Speicherlecks in JS. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:cnblogs.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen