【问题标题】:Javascript memory leak detectionJavascript内存泄漏检测
【发布时间】:2020-01-02 14:41:25
【问题描述】:

我写了一个非常简单的脚本来查询redis中列表的长度并输出内存使用情况。

随着时间的推移,“堆使用”内存似乎越来越高。

这是否推断出内存泄漏,以及如何更改此代码以防止这种情况发生?

输出:

[2020-01-02 16:59:09] Queue length > 0
[2020-01-02 16:59:09] Test queue length is 121
[2020-01-02 16:59:09] Heap total: 18.23MB, Heap Used: 8.43MB
[2020-01-02 16:59:11] Queue length > 0
[2020-01-02 16:59:11] Test queue length is 121
[2020-01-02 16:59:11] Heap total: 18.73MB, Heap Used: 8.70MB
[2020-01-02 16:59:13] Queue length > 0
[2020-01-02 16:59:13] Test queue length is 121
[2020-01-02 16:59:13] Heap total: 18.73MB, Heap Used: 8.72MB
...
[2020-01-02 17:03:53] Queue length > 0
[2020-01-02 17:03:53] Test queue length is 121
[2020-01-02 17:03:53] Heap total: 18.73MB, Heap Used: 11.17MB

代码:

const Redis           = require('ioredis');
const redis           = new Redis();
const dateformat      = require('dateformat');
const log             = console.log;


console.log = function () {
  let output = ['[',dateformat(new Date(), "yyyy-mm-dd HH:MM:ss")];
  output.push(']');
  output = [output.join('')]
  output = output.concat([].slice.call(arguments));
  log.apply(console,
            output
          );
}

function loop() {
   redis.llen('test').then( (queue_length) => {
       if (queue_length > 0) {
           console.log("Queue length > 0")
           }
       console.log(`Test queue length is ${queue_length}`)
       let m = process.memoryUsage()
       console.log(`Heap total: ${(m['heapTotal']/1024/1024).toFixed(2)}MB, Heap Used: ${(m['heapUsed']/1024/1024).toFixed(2)}MB`);
   })
}

setInterval(loop, 2000)

编辑:

最终我看到我只能假设是垃圾收集:

[2020-01-02 17:05:41] Queue length > 0
[2020-01-02 17:05:41] CDR queue length is 121
[2020-01-02 17:05:41] Heap total: 13.73MB, Heap User: 8.63MB

我不确定随着内存继续上升,是否仍然推断出泄漏,然后在垃圾回收后下降。最佳做法是在 setInterval 循环期间清除变量分配,还是将其留给 GC?

【问题讨论】:

  • 有一些手动清除指向非常大数据结构的变量的用例,但大多数情况下,在 Javascript 中,您只需让 GC 完成它的工作,并确保您不保留对大数据结构的引用永久变量中不再需要的数据结构(在顶级范围内声明的变量)或在数组等结构中累积不再需要的数据。仅供参考,清除对大型数据结构的引用并不会在那时将其从内存中删除 - 它只是在 GC 决定运行时使其符合 GC 条件。
  • 谢谢@jfriend00,你已经非常简单地回答了我的另一个问题!我相信这个评论简洁地回答了这个问题,如果你想把它作为答案发布,我会这样标记它。谢谢!

标签: javascript node.js memory-management


【解决方案1】:

有一些手动清除指向非常大数据结构的变量的用例,但大多数情况下,在 Javascript 中,您只需让 GC 完成它的工作,并确保您不会保留对大型数据结构的引用在永久变量中不再需要(在顶级范围内声明的变量)或在数组等结构中累积不再需要的数据。

仅供参考,当时清除对大型数据结构的引用不会将其从内存中删除 - 它只是在 GC 决定运行时使其符合 GC 条件(如果没有其他变量指向相同的数据)。


Javascript 中有几种情况需要注意,这可能会让人们感到惊讶,这通常是因为 Javascript 中的函数作用域和该作用域中的局部变量可以在函数本身完成后很长时间仍然存在正在执行。

这是一个简单的例子:

function startTimer() {
    let bigVar = someGiantDataStructure;
    return setInterval(function() {
        if (someCondition) {
            ++bigVar.someProperty;
        } else {
            // do something else
        }
    }, 1000);
}

let intervalID = startTimer();

因此,即使startTimer() 运行并完成,它也会启动间隔计时器,该计时器继续定期调用其回调。因为理论上回调仍然可能引用bigVar,所以bigVar 指向的数据永远不会被垃圾回收,直到有人停止间隔计时器。

因此,如果您在处理计时器时发生某些事情,并且您知道 someCondition 将永远不会再次为真,或者将永远不再需要 bigVar 的内容,但间隔计时器由于其他原因继续存在,那么它做类似bigVar = null; 这样的事情是有意义的,以允许它指向的数据有资格进行垃圾收集。

【讨论】:

  • @AndrewWhite - 我添加了一个需要注意的情况来回答我的问题。
  • 哦,这很有趣,感谢您的澄清!我会假设完成脚本的执行也会清理这个分配,并且在退出前不需要专门清除间隔或变量?
  • @AndrewWhite - 当 node.js 进程存在时,所有 Javascript 内存将被释放(连同进程使用的所有内存),因此当应用程序退出时不需要手动清除任何内容。也不需要停止间隔。如上所示的间隔计时器将导致进程不会自动退出(因为 node.js 认为仍有代码在做某事),但如果您手动调用process.exit(),它将退出并且 node.js 使用的所有内存将自动返回操作系统。无需关闭内存清理。
猜你喜欢
  • 2012-07-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-22
  • 2015-02-28
  • 2013-11-14
相关资源
最近更新 更多