JavaScript 导致大量内存泄漏

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

在过去的几天里,我们的家用电脑多次意外地突然停止运行。 UI大约需要半个小时才能恢复,有时需要硬重启。打开任务管理器显示某个特定的 PID 正在使用大约 96% 的 PC RAM,并且使用 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();
    }

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

            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);
    }

最佳答案

最重要的是,您似乎对无限循环感到困惑。当您输入某个浏览器时,某些浏览器挂起。例如,将其输入到 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 循环继续其工作(刷新用户界面)直到两秒过去。

<小时/>

回答您的问题:

Is there a best practice to modify code to stop memory leaks?

在设计软件时投入大量的批判性思考,以便对象不会不必要地闲置。

Is there any way to run indefinite loops without causing memory leaks?

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

... It's quite trivial to initialize a collection at the start of a program, fill it with tons of objects with destructors, and then enter an infinite event loop that never refers to it. The collection will sit around uselessly, holding on to its precious resources until the program terminates (at which point all those resources would have been reclaimed by the OS anyway).

换句话说,它取决于循环以及循环之前发生的事情!

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

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

Or some way to release these resources that are apparently not being released?

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

关于JavaScript 导致大量内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44060459/

相关文章:

javascript - 在javascript中,当一个函数名被指向其他地方后,有什么方法可以到达那个未命名的函数吗?

c# - 在不使用 List 的情况下如何在 C# 中执行此操作?

c - brk() 系统调用有什么作用?

c++ - 访问结构的char成员变量地址

c - 将元素添加到 fifo 队列

c# - jquery 自动完成 asp.net Gridview 中的多个文本框

javascript - 在js中用多个定界符和特殊字符分割

javascript - 找到位于二次曲线上的两点之间的控制点

javascript - 如何连接(变量+对象键名)以点表示法获取对象值

css - 响应式字体媒体查询少循环