Javascript 为什么v8在这种情况下会耗尽内存?

Javascript 为什么v8在这种情况下会耗尽内存?,javascript,node.js,google-chrome,garbage-collection,v8,Javascript,Node.js,Google Chrome,Garbage Collection,V8,根据node.js文档,节点在32位版本上有512meg限制,在64位版本上有1.4gig限制。铬合金AFAICT的限值类似。(+/-25%) 那么,为什么这段代码使用的内存从未超过424meg 下面是代码(代码毫无意义。这个问题不是关于代码在做什么,而是关于代码失败的原因) 已尝试节点4.2.4和5.6.0 所以,问题是为什么它的内存不足 有些事情我试过了 我试着把块连接起来 我没有无限期地附加到输出,而是尝试检查它是否正确 大于某些尺寸(如8k)。如果是这样,我把它放在一个数组中 将输出重置

根据node.js文档,节点在32位版本上有512meg限制,在64位版本上有1.4gig限制。铬合金AFAICT的限值类似。(+/-25%)

那么,为什么这段代码使用的内存从未超过424meg

下面是代码(代码毫无意义。这个问题不是关于代码在做什么,而是关于代码失败的原因)

已尝试节点4.2.4和5.6.0

所以,问题是为什么它的内存不足

有些事情我试过了

  • 我试着把块连接起来

    我没有无限期地附加到
    输出
    ,而是尝试检查它是否正确 大于某些尺寸(如8k)。如果是这样,我把它放在一个数组中 将输出重置为空字符串

    通过这样做,
    输出永远不会超过8k。这个阵列可以容纳
    180meg+记账。所以180meg+8k比180meg+
    180兆。它的内存仍然不足。现在,在这个过程结束时,我
    在实际使用更多内存时加入阵列
    (180兆欧+180兆欧+簿记)。但是,v8在达到这个目标之前就崩溃了
    线路

  • 我试着把编码改成

    function encode(num) {
      return 'X';
    }
    
    在这种情况下,它实际上运行到完成!!所以我想,“哈哈! 问题必须与
    查找[num]
    生成相关 每次通话都会有新的字符串?所以我试着

  • 查找更改为字符串数组

    var lookup = Array.prototype.map.call(
        'superCaliFragilisticExpialidosiousThispartdoesnotrealllymattersd', 
        function(c) {
          return c;
        });
    
    内存仍然不足

  • <--- Last few GCs --->
    
        3992 ms: Scavenge 1397.9 (1458.1) -> 1397.9 (1458.1) MB, 0.2 / 0 ms (+ 1.5 ms in 1 steps since last GC) [allocation failure] [incremental marking delaying mark-sweep].
        4450 ms: Mark-sweep 1397.9 (1458.1) -> 1397.9 (1458.1) MB, 458.0 / 0 ms (+ 2.9 ms in 2 steps since start of marking, biggest step 1.5 ms) [last resort gc].
        4909 ms: Mark-sweep 1397.9 (1458.1) -> 1397.9 (1458.1) MB, 458.7 / 0 ms [last resort gc].
    
    $ node foo.js    
    <--- JS stacktrace --->
    
    ==== JS stack trace =========================================
    
    Security context: 0x3a8521e3ac1 <JS Object>
        2: makeString(aka makeString) [/Users/gregg/src/foo.js:~6] [pc=0x1f83baf53a3b] (this=0x3a852104189 <undefined>,uint8=0x2ce813b51709 <an Uint8Array with map 0x32f492c0a039>)
        3: test(aka test) [/Users/gregg/src/foo.js:19] [pc=0x1f83baf4df7a] (this=0x3a852104189 <undefined>)
        4: /* anonymous */ [/Users/gregg/src/foo.js:24] [pc=0x1f83baf4d9e5] (this=0x2ce813b...
    
    FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory
    Abort trap: 6
    
    这看起来像是v8中的一个bug?由于这段代码,它无法以某种奇怪的方式GC未使用的字符串,尽管#2 vs#3很奇怪,因为它们在内存使用方面似乎相当


    为什么v8在这些情况下会耗尽内存?(是否有解决办法)

    TL;DR:您的示例是v8的一个内部字符串表示的病态情况。您可以通过偶尔索引到
    输出
    来修复它(下面将介绍原因)

    首先,我们可以使用查看垃圾收集器的用途:

    上面的快照是在节点内存不足前不久拍摄的。正如您所看到的,大多数情况看起来都很正常:我们看到两个字符串(非常大的
    输出和要添加的小数据块),对同一数组的三个引用
    大的
    (大约64MB,与我们预期的类似),以及许多看起来并不特别的小物件

    但是,有一件事很突出:
    输出
    的容量高达1.4+GB。在拍摄快照时,它大约有8000万个字符长,所以假设每个字符有2个字节,那么大约有160MB。这怎么可能呢

    这可能与v8的内部字符串表示有关。引用:

    有两种类型的[v8字符串](实际上更多,但对于手头的问题,只有这两种是重要的):

    • 平面字符串是不可变的字符数组
    • cons字符串是字符串对,是串联的结果
    如果将a和b合并,则会得到一个表示合并结果的cons字符串(a,b)。如果稍后将d合并到该字符串,则会得到另一个cons字符串((a,b,d)

    为这样一个“树状”字符串编制索引不是O(1),因此,为了加快编制索引的速度,V8会在编制索引时展平字符串:将所有字符复制到一个展平字符串中

    那么可能是v8将
    输出表示为一棵巨树吗?检查的一种方法是强制v8压扁字符串(如上面mraleph所建议的),例如,在
    for
    循环内定期索引到
    output

    if (i % 10000000 === 0) {
      // We don't do it at each iteration since it's relatively expensive.
      output[0];
    }
    
    事实上,程序成功运行了


    还有一个问题:为什么上面的版本2会运行?在这种情况下,v8似乎能够优化掉大多数字符串连接(所有位于右侧的字符串连接,它们在4元素数组上转换为按位操作)。

    有趣的修复方法。我想知道它将继续修复多长时间:(这并不能真正解释为什么#1不起作用。因为
    输出
    每8192个字符被重置为空字符串,所以v8似乎应该能够丢弃之前的cons字符串。我假设每个块都是8192深度的cons字符串,这仍然足以杀死它。也许我应该尝试添加字符(或三元组)并将其加入数组。因此没有cons字符串。对,在这种情况下,您仍然会得到许多cons字符串。使用数组(甚至缓冲区)这确实是一种更安全的方式。大多数垃圾收集器在内存不足时会冻结世界,并回收任何可能回收的内存。如果您感到好奇,请参阅公认的答案,了解内存不足的原因
    if (i % 10000000 === 0) {
      // We don't do it at each iteration since it's relatively expensive.
      output[0];
    }