34

我想了解哪种代码会导致 JavaScript 中的内存泄漏,并创建了下面的脚本。但是,当我在 OS X 上的 Safari 6.0.4 中运行脚本时,活动监视器中显示的内存消耗并没有真正增加。

我的脚本有问题还是现代浏览器不再有问题?

<html>
<body>
</body>
<script>
var i, el;

function attachAlert(element) {
    element.onclick = function() { alert(element.innerHTML); };
}

for (i = 0; i < 1000000; i++) {
    el = document.createElement('div');
    el.innerHTML = i;
    attachAlert(el);
}
</script>
</html>

该脚本基于 Google 的 JavaScript 样式指南的 Closure 部分:http: //google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml ?showone=Closures#Closures

编辑:导致上述代码泄漏的错误显然已得到修复:http: //jibbering.com/faq/notes/closures/#clMem

但我的问题仍然存在:是否有人能够提供一个现实的 JavaScript 代码示例,该示例会在现代浏览器中泄漏内存?

Internet 上有许多文章表明内存泄漏可能是复杂单页应用程序的一个问题,但我很难找到可以在浏览器中运行的示例。

4

7 回答 7

12

您没有保留您在任何地方创建和引用的元素 - 这就是您没有看到内存使用量增加的原因。尝试将元素附加到 DOM,或将其存储在对象中,或将 onclick 设置为不同的元素。然后你会看到内存使用量猛增。垃圾收集器将通过并清理任何不再被引用的东西。

基本上是您的代码的演练:

  • 创建元素 (el)
  • 创建一个引用该元素的新函数
  • 将函数设置为该元素的 onclick
  • 用新元素覆盖元素

一切都以存在的元素为中心。一旦无法访问元素,就无法再访问 onclick。因此,由于无法访问 onclick,因此创建的函数被销毁......并且该函数具有对元素的唯一引用......所以元素也被清理了。

有人可能有一个更技术性的例子,但这是我理解 javascript 垃圾收集器的基础。

编辑:这是脚本泄漏版本的多种可能性之一:

<html>
<body>
</body>
<script>
var i, el;

var createdElements = {};
var events = [];

function attachAlert(element) {
    element.onclick = function() { alert(element.innerHTML); };
}

function reallyBadAttachAlert(element) {
    return function() { alert(element.innerHTML); };
}

for (i = 0; i < 1000000; i++) {
    el = document.createElement('div');
    el.innerHTML = i;

    /** posibility one: you're storing the element somewhere **/
    attachAlert(el);
    createdElements['div' + i] = el; 

    /** posibility two: you're storing the callbacks somewhere **/
    event = reallyBadAttachAlert(el);
    events.push(event);
    el.onclick = event;

}
</script>
</html>

因此,对于#1,您只是在某处存储对该元素的引用。你永远不会使用它并不重要 - 因为该引用是在对象中进行的,元素及其回调永远不会消失(或者至少在你从对象中删除元素之前)。对于可能性 #2,您可以将事件存储在某处。因为即使找不到元素也可以访问事件(即通过执行events[10]();),它仍然被事件引用..所以元素将与事件一样保留在内存中,直到它从数组中删除。

于 2013-04-27T20:25:33.643 回答
2

update: Here is a very simple example based on the caching scenario in the Google I/O presentation:

/*
This is an example of a memory leak. A new property is added to the cache
object 10 times/second. The value of performance.memory.usedJSHeapSize
steadily increases.

Since the value of cache[key] is easy to recalculate, we might want to free
that memory if it becomes low. However, there is no way to do that...

Another method to manually clear the cache could be added, but manually
adding memory checks adds a lot of extra code and overhead. It would be
nice if we could clear the cache automatically only when memory became low.

Thus the solution presented at Google I/O!
*/

(function(w){
    var cache = {}
    function getCachedThing(key) {
        if(!(key in cache)) {
            cache[key] = key;
        }
        return cache[key];
    }

    var i = 0;
    setInterval(function() {
        getCachedThing(i++);
    }, 100);
    w.getCachedThing = getCachedThing
})(window);

Because usedJSHeapSize does not update when the page is opened from the local file system, you might not see the increasing memory usage. In that case, I have hosted this code for you here: https://memory-leak.surge.sh/example-for-waterfr


This Google I/O'19 presentation gives examples of real-world memory leaks as well as strategies for avoiding them:

  • Method getImageCached() returns a reference to an object, also caching a local reference. Even if this reference goes out of the method consumer's scope, the referenced memory cannot be garbage collected because there is a still a strong reference inside the implementation of getImageCached(). Ideally, the cached reference would be eligible for garbage collection if memory got too low. (Not exactly a memory leak, but a situation where there is memory that could be freed at the cost of running the expensive operations again).
  • Leak #1: the reference to the cached image. Solved by using weak references inside getImageCached().
  • Leak #2: the string keys inside the cache (Map object). Solved by using the new FinalizationGroup API.

Please see the linked video for JS code with line-by-line explanations.

More generally, "real" JS memory leaks are caused by unwanted references (to objects that will never be used again). They are usually bugs in the JS code. This article explains four common ways memory leaks are introduced in JS:

  1. Accidental global variables
  2. Forgotten timers/callbacks
  3. Out of DOM references
  4. Closures

An interesting kind of JavaScript memory leak documents how closures caused a memory leak in the popular MeteorJS framework.

于 2019-05-14T18:30:19.220 回答
1

我试图做类似的事情并且内存异常。

const test = (array) => {
  array.push((new Array(1000000)).fill('test'));
};

const testArray = [];

for(let i = 0; i <= 1000; i++) {
  test(testArray);
}
于 2019-05-15T15:51:11.863 回答
1

2020 年更新:

大多数 CPU 端内存溢出不再适用于基于现代 v8 引擎的浏览器。但是,我们可以通过运行这个脚本来溢出 GPU 端的内存

// Initialize canvas and its context
window.reallyFatCanvas = document.createElement('canvas');
let context = window.reallyFatCanvas.getContext('2d');

// References new context inside context, in loop.
function leakingLoop() {
    context.canvas.width = document.body.clientWidth;
    context.canvas.height = document.body.clientHeight;
    const newContext = document.createElement('canvas').getContext('2d');
    context.context = newContext;
    context.drawImage(newContext.canvas, 0, 0);
    
    // The new context will reference another context on the next loop
    context = newContext;
}

// Use interval instead of while(true) {...}
setInterval(leakingLoop,1);

编辑:我重命名每个变量(和常量),所以它很有意义。这是解释。

根据我的观察,画布上下文似乎与视频内存同步。因此,如果我们将一个画布对象的引用也引用另一个画布对象等等,那么视频 RAM 会比 DRAM 填充更多,在 microsoft edge 和 chrome 上进行测试。

这是我第三次尝试截图:使用 JavaScript 的 VRAM 泄漏

我不知道为什么我的笔记本电脑在运行此脚本时总是在截屏后冻结几秒钟。如果您想尝试该脚本,请小心。

于 2020-08-04T17:30:02.663 回答
0

如果您只想创建内存泄漏,那么 IMO 最简单的方法是实例化一个TypedArray,因为它们占用了固定大小的内存并且比任何引用都长。例如,Float64Array使用2^27元素创建一个会消耗 1GiB ( 1 Gibibyte ) 的内存,因为每个元素需要 8 个字节

启动控制台并编写以下代码:

new Float64Array(Math.pow(2, 27))
于 2019-05-15T10:41:55.533 回答
0

最简单的方法是:

while(true){}
于 2020-08-09T12:34:53.833 回答
0

导致 1MB 内存泄漏的代码小示例:

Object.defineProperty(globalThis, Symbol(), {value: new Uint8Array(1<<20).slice(), writable: false, configurable: false})

运行该代码后,释放泄漏内存的唯一方法是关闭运行它的选项卡。

于 2021-03-17T13:56:26.680 回答