Javascript导致MASSIVE内存泄漏

时间:2017-05-19 02:26:46

标签: javascript loops memory-management memory-leaks infinite-loop

在过去的几天里,我们的家用电脑突然意外地停了多次。它需要大约半小时的UI来恢复,有时需要硬重启。打开任务管理器显示一个特定的PID正在使用大约96%的PC内存,并且使用Chrome任务管理器,我已经确定它是我制作的网页。

我对内存泄漏知之甚少,因为我只是顺便听说过它们,但我几乎可以肯定我的Javascript代码以某种方式导致了内存泄漏。是否有最佳实践来修改代码以阻止内存泄漏?我怀疑泄漏是由response()函数引起的。

在对这个问题进行复制或没有提供研究之前,this other Stack Overflow question让我相信case "12124561414":可能是罪魁祸首;但是,我无法测试这个。此外,此函数中包含的循环不执行,因此我不确定这是否真的是罪魁祸首,因为我不知道有任何内置的JS机制来隔离内存泄漏。

有没有办法在不导致内存泄漏的情况下运行无限循环? (如果这确实是我遇到巨大内存泄漏的原因)或某种方式来释放显然被释放的资源?我的目标是让忙碌的音调一遍又一遍地重复,直到页面刷新为止,但我认为没有理由让计算机崩溃。

此网页的完整JS代码如下:

    var availableNumbers = ["0", "911", "1 (847) 765-1008" , "867-5309", "1 (212) 456-1414", "555-1212", "555-5555"];
    function numberSuggestion() {
        var randomNumber = Math.floor(Math.random() * (availableNumbers.length));
        var suggestedNumber = availableNumbers[randomNumber];
        document.getElementById("suggestion").innerHTML = "How about dialing <strong id='suggestedTelephoneNumber'>" + suggestedNumber + "</strong>? Don't like this number? Click the button above again!";
    }
    var dialTone;
    function offHook() {
        document.getElementById("WE2500").style.display = "none";
        document.getElementById("dialPad").style.display = "block";
        dialTone = new Audio('dialTone.m4a');
        dialTone.play();
    }
    var number = "";
    var timeout;
    function numberDial() {
        if (dialTone) {
            dialTone.pause();
            dialTone.currentTime = 0;
        }
        clearTimeout(timeout);
        timeout = setTimeout(dial, 2000);
    }
    function dial1() {
        numberDial();
        number = number + "1";
        var tone1 = new Audio('DTMF-1.wav');
        tone1.play();
    }
    function dial2() {
        numberDial();
        number = number + "2";
        var tone2 = new Audio('DTMF-2.wav');
        tone2.play();
    }
    function dial3() {
        numberDial();
        number = number + "3";
        var tone3 = new Audio('DTMF-3.wav');
        tone3.play();
    }
    function dial4() {
        numberDial();
        number = number + "4";
        var tone4 = new Audio('DTMF-5.wav');
        tone4.play();
    }
    function dial5() {
        numberDial();
        number = number + "5";
        var tone5 = new Audio('DTMF-5.wav');
        tone5.play();
    }
    function dial6() {
        numberDial();
        number = number + "6";
        var tone6 = new Audio('DTMF-6.wav');
        tone6.play();
    }
    function dial7() {
        numberDial();
        number = number + "7";
        var tone7 = new Audio('DTMF-7.wav');
        tone7.play();
    }
    function dial8() {
        numberDial();
        number = number + "8";
        var tone8 = new Audio('DTMF-8.wav');
        tone8.play();
    }
    function dial9() {
        numberDial();
        number = number + "9";
        var tone9 = new Audio('DTMF-9.wav');
        tone9.play();
    }
    function dial0() {
        numberDial();
        number = number + "0";
        var tone0 = new Audio('DTMF-0.wav');
        tone0.play();
    }
    function dialStar() {
        numberDial();
        number = number + "*";
        var toneStar = new Audio('DTMF-star.wav');
        toneStar.play();
    }
    function dialPound() {
        numberDial();
        number = number + "#";
        var tonePound = new Audio('DTMF-pound.wav');
        tonePound.play();
    }
    var ringingTone = new Audio('DTMF-ringbackTone.mp3');
    var timesRung = 0;
    function dial() {
        function ring() {
            ringingTone.play();
            timesRung++;
            if (timesRung > 1) {
                setTimeout(response, 700);
            }
        }
        ring();
        setTimeout(ring, 4000);
    }
    function response() {
        switch(number) {
            case "0":
                var operatorPickup = new Audio('OperatorAnswer.wav');
                operatorPickup.addEventListener("ended", function(){
                    number = prompt("Operator, your number please? (Numbers only; enter 'police' for police and emergency)");
                    if (number == null) {
                        number = "0";
                    }
                    operatorPutCallThrough();
                });
                operatorPickup.play();
                break;
            case "911":
                var pickup911 = new Audio('911-xxx-fleet.mp3');
                pickup911.play();
                break;
            case "18477651008":
                var pickupMCI = new Audio('MCI.wav');
                pickupMCI.play();
                break;
            case "8675309":
                var pickup8675309 = new Audio('discoornis-bell-f1.mp3');
                pickup8675309.play();
                break;
            case "12124561414":
                var pickupBusy = new Audio('tele-busy.wav');
                console.log(number);
                while (number == "12124561414") {
                    pickupBusy.play();
                    pickupBusy.currentTime = 0;
                }
                break;
            case "5551212":
                var pickupLocalInfo = new Audio('tele-busy.wav');
                pickupLocalInfo.play();
                break;
            case "5555555":
                var pickup5555555 = new Audio('timeout-bell-f1.mp3');
                pickup5555555.play();
                break;
            case "police":
                break;
            default:
                var pickupDefault = new Audio('ldcircuits-bell-f1.mp3');
                pickupDefault.play();
        }
    }
    function operatorPutCallThrough() {
        alert("One moment please, ringing the line now.");
        if (number == "police") {
            var operatorPoliceTransfer = new Audio('OperatorPolice.wav');
            operatorPoliceTransfer.play();
        }
        response();
    }

我的预感是这可能是一个更好的方法,但我有点害怕测试它以防PC再次崩溃。

            case "12124561414":
                busy();
                break;
            case "5551212":
                busy();
                break;
            case "5555555":
                var pickup5555555 = new Audio('timeout-bell-f1.mp3');
                pickup5555555.play();
                break;
            case "police":
                break;
            default:
                var pickupDefault = new Audio('ldcircuits-bell-f1.mp3');
                pickupDefault.play();
        }
    }
    function busy() {
        function() busyTone {
            var pickupBusy = new Audio('tele-busy.wav');
            pickupBusy.play();
            pickupBusy.currentTime = 0;
        }
        setInterval(function() busyTone, 1);
    }

1 个答案:

答案 0 :(得分:1)

最重要的是,你似乎对无限循环感到困惑。当您输入一个浏览器挂起。例如,将其输入到JS控制台中,您可能会看到类似于您的挂起:

var number = "12124561414";
while (number == "12124561414") { /* do nothing */ }

这里的问题是while循环在与GUI循环相同的线程中运行,因此GUI将锁定直到该循环停止。

此问题的解决方案以与您在此处提供的其他代码类似的形式呈现。例如,此代码在一段时间后播放一次音调,允许GUI在该段时间内更新:

function numberDial() {
    if (dialTone) {
        dialTone.pause();
        dialTone.currentTime = 0;
    }
    clearTimeout(timeout);
    timeout = setTimeout(dial, 2000);
}

改为使用setInterval,并且每2秒调用一次dial,直到您使用clearInterval停止它,允许GUI循环继续其工作(刷新用户界面) )直到那两秒钟过去了。

回答你的问题:

  

是否有最佳做法修改代码以阻止内存泄漏?

在设计软件时要付出沉重的批判性思考,以免物体不必要地挂在上面。

  

有没有办法在不造成内存泄漏的情况下运行无限循环?

我认为Rust doc很好地回答了这个问题......

  

...在程序开始时初始化一个集合,用大量带有析构函数的对象填充它,然后输入一个永远不会引用它的无限事件循环,这是非常简单的。该系列将毫无用处地存在,保留其宝贵的资源,直到程序终止(此时所有这些资源都将被操作系统收回)。

换句话说,它取决于循环,以及之前循环

让我们考虑在您的(浏览器挂起)循环之前的代码行中发生了什么:console.log(number);。我相信 this 是您已识别的代码中唯一可能的泄漏,因为这会导致对作为对象的number的引用进行存储进入这样的集合( debug 日志)。

从进一步的分析中可以看出,这可能不是泄漏,因为number无论如何在函数之外声明......但值得注意的是, console.log 可以防止对象被视为垃圾

  

或者某种方式来释放那些显然被释放的资源?

我建议确保垃圾收集器可以通过有条件地记录来减少存储的数量来回收这些资源。实际上,您推送到日志的引用也将存储其他引用(例如,返回到全局对象)。