【问题标题】:memory leak in node regex parser?节点正则表达式解析器中的内存泄漏?
【发布时间】:2015-07-31 17:08:19
【问题描述】:

以下代码导致节点消耗大量内存并在内存不足时崩溃。但是,如果我将找到的字符串的长度从 13 更改为 12,一切都很好。看起来正则表达式搜索返回的字符串包含对搜索的原始字符串的隐藏引用。但前提是找到的匹配项的长度至少为 13 个字符。 这是一个错误还是这种行为有什么好的理由?

function randString(length) {
  var a = "a".charCodeAt(0),
      result = [];
  for(var i = 0; i < length; i++) {
    result.push(a + Math.floor(Math.random() * 26));
  }
  return String.fromCharCode.apply(null, result);
}


var arr = [];

for(var i = 0; i < 1000000; i++) {
  if(i % 1000 === 0) console.log(i);
  var str = randString(13);
  str = randString(5000) + "<" +str + ">" + randString(5000);
  var re = /<([a-z]+)>/gm;
  var next = re.exec(str);
  arr.push(next[1]);
}

【问题讨论】:

  • 您确定 12 个字符的版本也没有占用大量内存,但不足以使其崩溃吗?
  • 12 个字符的版本使用少于 100 MB。但如果有 13 个或更多字符,则只需 145,000 次迭代即可达到 1.5GB。
  • 我在 Chrome 中观察到同样的事情(版本 41.0.2272.76 构建于 Ubuntu 14.04,运行于 LinuxMint 17(64 位))

标签: javascript regex node.js memory-leaks


【解决方案1】:

我找到了问题的根源。负责此问题的不是正则表达式解析器,而是字符串上的子字符串方法。它旨在作为使创建子字符串更有效的功能。 在 V8 错误报告页面上有一个未解决的问题。 https://code.google.com/p/v8/issues/detail?id=2869

【讨论】:

    【解决方案2】:

    我在 Chrome 中观察到相同的行为。我认为两者(node.js 和 Chrome)的行为相同,因为它们基于相同的 Javascript 引擎(V8)。

    没有内存泄漏,但是Javascript中的垃圾管理有问题。我从观察中推断出这一点,当我在 Google Dev Tools 中强制进行垃圾收集时,释放了 GB 的内存。

    您可以强制运行垃圾收集器,如explained here。这样,你的 node.js 就不会崩溃。

    编辑

    进一步测试我可以告诉这些事情:

    关于您的评论但只要仍然有对数组的引用,就不会释放内存。

    它看起来比这更复杂,但你是对的,arr 似乎占据了所有空间 1.1 Go for 100'000 items,这是每个项目 10kB。当您查看数组 next 时,它确实有大约 10kB 的大小(next.input 为 10015 字节。如果一切正常,next[1] 将是一个简单的字符串,并且仅使用略多于 13 个数据字节,但事实并非如此。在数组arr 中引用next[1] 不允许对next 进行垃圾回收。

    作为解决方案,我想出了这个修改后的代码 (fiddle):

    function randString(length) {
      var a = "a".charCodeAt(0),
          result = [];
      for(var i = 0; i < length; i++) {
        result.push(a + Math.floor(Math.random() * 26));
      }
      return String.fromCharCode.apply(null, result);
    }
    
    
    var arr = [];
    
    for(var i = 0; i < 100000; i++) {
      if(i % 1000 === 0) console.log(i);
      var str = randString(13);
      str = randString(5000) + "<" +str + ">" + randString(5000);
      var re = /<([a-z]+)>/gm;
      var next = re.exec(str);
      arr.push(next[1].split('').join(''));
    }
    console.log(arr)
    

    诀窍是通过拆分字符串并再次连接来切断next 和存储在arr 中的字符串之间的引用。

    我对内部结构一无所知,但它看起来像是 V8 中的一个错误。在 Firefox 上测试,一切正常,没有过多的内存使用。

    【讨论】:

    • 运行垃圾收集器没有任何效果。你可能在一个函数中定义了arr,当它返回时arr可以被垃圾回收。但只要还有对数组的引用,就不会释放内存。
    • 我无法在 node.js 上进行测试,但在 Google Chrome 中,垃圾回收会释放大部分内存。看看这个小提琴jsfiddle.net/lmeyer1/c2545qmd/2,运行后点击devtools的Timeline选项卡中的Collectgarbage
    • 好吧,它变得越来越有趣了。当我最后 console.log(arr) (以保留参考)时, Collect 垃圾 按钮不再收集,但是当我再次启动时(将参考保留在控制台),垃圾收集器会自动跳入。
    • 是的,同时控制台失去了对数组的引用。因此,您无法再扩展它。 jsfiddle 中的 js 代码也不能在顶层运行。它被包装在一个函数中。如果你在控制台中输入arr,它会给你一个参考错误。
    • 你是对的。我没有注意到控制台中的对象不再是可扩展的。我无法解释这种奇怪的行为,但至少我找到了一个有效的解决方法。查看我的编辑。
    猜你喜欢
    • 1970-01-01
    • 2011-01-11
    • 1970-01-01
    • 1970-01-01
    • 2017-09-03
    • 1970-01-01
    • 2015-10-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多