如何在JavaScript中创建内存泄漏?

时间:2013-04-27 20:16:49

标签: javascript memory-leaks garbage-collection closures

我想了解哪种代码会导致JavaScript中的内存泄漏并在下面创建脚本。但是,当我在OS X上的OS 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代码的真实示例?

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

7 个答案:

答案 0 :(得分:10)

你没有保留你在周围创建并在任何地方引用的元素 - 这就是你没有看到内存使用量增加的原因。尝试将元素附加到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]();),即使元素无处可寻,它仍然被事件引用..所以元素将保留在内存和事件中,直到它被删除来自阵列。

答案 1 :(得分:1)

更新:以下是一个非常简单的示例,基于Google I / O演示文稿中的缓存方案:

/*
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);

由于usedJSHeapSize does not update when the page is opened from the local file system,您可能看不到内存使用量的增加。在这种情况下,我已经在这里为您托管了以下代码:https://memory-leak.surge.sh/example-for-waterfr


This Google I/O'19 presentation给出了现实世界中内存泄漏的示例以及避免泄漏的策略:

  • 方法getImageCached()返回对对象的引用,同时也缓存本地引用。即使此引用超出方法使用者的范围,也无法对垃圾回收内存进行引用,因为getImageCached()的实现中仍存在一个强引用。理想情况下,如果内存太低,则缓存的引用将有资格进行垃圾回收。 (不是确切地发生内存泄漏,而是有可能以再次运行昂贵的操作为代价释放内存的情况。)
  • 泄漏#1:对缓存图像的引用。通过在getImageCached()内部使用弱引用来解决。
  • 泄漏#2:缓存(地图对象)内部的字符串键。使用新的FinalizationGroup API即可解决。

请观看链接的JS代码视频,其中逐行进行解释。

更一般地说,“真实的” JS内存泄漏是由不需要的引用(指向不再使用的对象)引起的。它们通常是JS代码中的错误。本文介绍了four common ways memory leaks are introduced in JS

  1. 偶然的全局变量
  2. 忘记了计时器/回调
  3. 没有DOM引用
  4. 关闭

An interesting kind of JavaScript memory leak介绍了闭包如何在流行的MeteorJS框架中导致内存泄漏。

答案 2 :(得分: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的填充量要远远超过在Microsoft Edge和chrome上测试过的DRAM。

这是我第三次尝试截屏:VRAM Leak with JavaScript

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

答案 3 :(得分:1)

最简单的方法是:

while(true){}

答案 4 :(得分:0)

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

启动控制台,只需编写以下内容:

new Float64Array(Math.po2(2, 27))

答案 5 :(得分:0)

我试图做这样的事情,但内存不足。

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

const testArray = [];

for(let i = 0; i <= 1000; i++) {
  test(testArray);
}

答案 6 :(得分:0)

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

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

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

相关问题