Heim  >  Artikel  >  Web-Frontend  >  Garbage Collection und Speicherlecks in JavaScript

Garbage Collection und Speicherlecks in JavaScript

藏色散人
藏色散人nach vorne
2019-05-06 09:33:292301Durchsuche

Vorwort

Die Ausführung des Programms erfordert Speicher. Wann immer 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 ist aus „The Definitive Guide to JavaScript (Fourth Edition)“ zitiert

Aufgrund von String-Objekte und Arrays haben keine feste Größe, sodass sie dynamisch zugewiesen werden können, wenn ihre Größe bekannt ist. 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.

Garbage Collection und Speicherlecks in JavaScript

Diese Passage erklärt, warum das System eine Garbage Collection benötigt. JavaScript ist nicht wie C/C++, es 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 Prozess ist jedoch nicht zeitaufwändig, da sein Overhead relativ groß ist Der Garbage Collector wird nach einem festen Intervall in regelmäßigen Abständen ausgeführt.

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 diese Leerzeichen vorhanden sind kann wiederverwendet werden.

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 verwendet werden können, solange der Ausführungsfluss in die entsprechende Umgebung eintritt. Wenn eine Variable die Umgebung verlässt, wird sie als „die Umgebung verlassen“ markiert.

Wenn der Garbage Collector ausgeführt wird, markiert er 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 schließt die Speicherbereinigung ab, zerstört die markierten Werte und gewinnt den von ihnen belegten Speicherplatz zurück.

Garbage Collection und Speicherlecks in JavaScript

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 so- „Referenzzählung“ bedeutet, dass die Sprach-Engine über eine „Referenztabelle“ verfügt, die die Anzahl der Referenzen auf alle Ressourcen (normalerweise verschiedene Werte) im Speicher speichert. 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.

Garbage Collection und Speicherlecks in JavaScript

Im Bild oben haben die beiden Werte in der unteren linken Ecke keine Referenzen, sodass sie freigegeben werden können.

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 der einzige Verweis auf diesen Wert, daher beträgt die Anzahl der Verweise 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, nämlich die Anzahl der Verweise auf das Array [1, 2, 3, 4 ] wird um 1 reduziert, zu diesem Zeitpunkt wird sein Referenzzähler 0, was bedeutet, dass es keine Möglichkeit mehr gibt, auf diesen Wert zuzugreifen, sodass der von ihm belegte Speicherplatz wiederhergestellt werden kann.

Aber das größte Problem bei der Referenzzählung ist: Zirkelverweis

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

Wenn die Funktion func ausgeführt wird, ist der Rückgabewert undefiniert, daher sollten die gesamte Funktion und die internen Variablen recycelt werden, aber entsprechend Bei der Referenzzählmethode ist die Anzahl der Referenzen von obj1 und obj2 nicht 0, sodass sie nicht recycelt werden.

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

obj1 = null;
obj2 = null;

3. Welche Situationen können zu Speicherverlusten führen?

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

Garbage Collection und Speicherlecks in JavaScript

Garbage Collection und Speicherlecks in 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

Das obige ist der detaillierte Inhalt vonGarbage Collection und Speicherlecks in JavaScript. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:浪里行舟. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen
Vorheriger Artikel:Wie schreibe ich Ajax in jsNächster Artikel:Wie schreibe ich Ajax in js