节点内存使用情况:拼接字符串

10
我有以下代码用于测试Node.js的vm模块的内存使用情况:
setInterval(()=>console.log(process.memoryUsage()),1000);

( ()=> {

    const MAXTIMES = 10000000;

    let a = ( ()=> {
        let res = "";
        for(let i=0; i<MAXTIMES; i++){
            res = res + "X";
        }
        return res;
    })();


})();

//Uncomment this to give enough time for GC and check top command. 
//Not needed with setInterval in the beginning
//setTimeout(()=>{}, 10000);

top 命令显示 VM 使用了约 420M 的内存。

当我将 i 的最大值更改为 100000000(100M)时,我原本期望的内存使用量应该在 620M 左右(420M +2 字节 * 100M),但实际却达到惊人的 3300~4000M。这是怎么回事呢?GC 没有清理循环中的临时字符串吗?

我尝试使用 var 来确保 hoist i,但结果相同。

编辑1:更新带注释的完整代码

编辑2

我还在 Firefox、Safari 和 Chrome JS 控制台的随机标签页上执行了该代码,并出现了类似的问题。Firefox 标签页占用了约 2.5G,Safari 标签页占用了约 6G 内存,Chrome 标签页占用了约 3G。在所有情况下,内存直到关闭标签页后才被 GC 清理。


3
为什么你没有预料到字符串大小增加十倍会导致内存使用量增加十倍?你能解释一下你是如何得出 "420M +2 bytes * 100M" 的吗? - Bergi
我有点期望得到一个原始字符串,像是一个字节用于X,一个字节用于/0,但我的期望是错误的,因为JS字符串是封装的。 - SwiftMango
1
内存使用情况是驻留集(代码段+堆栈+堆)而不是堆大小(),因此,请使用堆值进行任何测量。您可以使用此函数获取精确值:process.memoryUsage() - Ele
1
@texasbruce - 你能百分之百确定在测量之前所有的GC都已经运行了吗?根据我的经验,有时候需要一段时间才能运行完所有的GC。如果你手动调用GC,就可以更容易地确保它已经完成了。 - jfriend00
1
@jfriend00:“每次执行res +=“X”,都会创建一个全新的字符串并将其分配给res,然后前一个字符串就可以进行垃圾回收了。” - 这取决于正在生效的哪种字符串连接优化 - Bergi
显示剩余11条评论
2个回答

9
不同的JavaScript引擎使用略有不同的垃圾回收策略。Node JS使用v8,这是最初为Chrome构建的引擎,Chrome仍在使用。
要了解更多关于V8中垃圾回收如何工作的内容,我建议阅读这篇文章,虽然它有些陈旧,但至今仍然相关:https://strongloop.com/strongblog/node-js-performance-garbage-collection/ 您可以使用--trace-gc --log-gc选项运行node来查看垃圾收集器正在做什么。
在您的代码中,内存增加的比您预期的要多得多,因为:
每次执行res += "X"时,它都会创建一个全新的字符串并将其分配给res,之前的字符串就可以进行垃圾回收了。
正如@jfriend00在评论中所写的那样。实际上可能发生的事情比这更复杂,但我认为我们不需要深入细节。
我更喜欢向您展示,通过运行您的代码我们能够观察到什么变化。我稍微对它进行了修改。
const humanize = require('humanize')
setInterval(() => console.log(humanize.filesize(process.memoryUsage().heapUsed)),1000);

setInterval(() => {
    const MAXTIMES = 10000000;

    let a = ( ()=> {
        let res = "";
        console.log('starting');
        for(let i=0; i<MAXTIMES; i++){
            // Add this for last run
            // if (i % (MAXTIMES / 10) === 0) {
            //   console.log(i);
            // }
            res = res + "X";
        }
        console.log('done');
        return res;
    })();
}, 5000);

区别在于: 1)humanize 帮助格式化内存值。 2)我只打印 heapUsed。 3)我使用 setInterval 每 5 秒调用连接字符串的函数。
当我运行它时,我得到:
$ node index.js
4.36 MB
4.39 MB
4.40 MB
4.40 MB
starting
done
385.80 MB
385.80 MB
385.80 MB
385.80 MB
385.80 MB
starting
done
385.52 MB
4.00 MB
4.00 MB
4.00 MB
4.01 MB
starting
done
385.63 MB
385.63 MB
385.64 MB
385.64 MB
385.64 MB
starting
done
385.77 MB
385.77 MB
385.77 MB
385.78 MB
385.78 MB
starting
done
385.54 MB
...

因此,在调用函数之前,内存只有4Mb。由于它们是同步的,因此始终会依次打印“starting”和“done”。 在第一次调用后,heapUsed达到了385.80Mb
5秒钟后再次调用函数时,内存降至385.52 MB。请注意,内存没有翻倍。相反,在第二次运行中,内存在2秒后达到4.00 MB。之后,当再次调用函数时,内存再次回到385Mb--max-old-space-size限制了node可以使用的最大内存。 如果我再次运行程序并将内存限制为400MB,则输出不同:
$ node --max-old-space-size=400 index.js
4.36 MB
4.39 MB
4.40 MB
4.40 MB
starting
done
385.52 MB
384.04 MB
4.00 MB
4.00 MB
4.00 MB
starting
done
385.48 MB
4.00 MB
4.00 MB
4.00 MB
4.01 MB
starting
done
386.11 MB
384.21 MB
4.00 MB
4.00 MB
4.00 MB
starting
done
385.56 MB
4.00 MB

你可以看到,在函数调用后立即使用的内存约为 385Mb,但很快就会降至 4.00 MB
这是因为对内存的限制使垃圾回收算法变得更加“积极”,以便在避免内存不足错误的同时回收内存。
为了看到V8的真正作用,我们可以使用--trace-gc --log-gc参数。我还添加了if (i % (MAXTIMES / 10) === 0) console.log(i);到循环中,以确保清楚地知道何时发生事情。
$ node --trace-gc --log-gc --max-old-space-size=400  index.js
[10826:0x103000000]       51 ms: Scavenge 3.4 (6.4) -> 3.2 (7.4) MB, 1.3 / 0.0 ms  allocation failure
[10826:0x103000000]       61 ms: Scavenge 3.5 (7.4) -> 3.5 (8.4) MB, 1.1 / 0.0 ms  allocation failure
4.64 MB
4.67 MB
4.68 MB
4.68 MB
starting
0
[10826:0x103000000]     5094 ms: Scavenge 4.8 (8.9) -> 4.6 (8.9) MB, 1.1 / 0.0 ms  allocation failure
[10826:0x103000000]     5106 ms: Scavenge 5.5 (8.9) -> 5.5 (9.4) MB, 2.8 / 0.0 ms  allocation failure
[10826:0x103000000]     5109 ms: Scavenge 6.1 (9.4) -> 6.1 (10.4) MB, 2.7 / 0.0 ms  allocation failure
[10826:0x103000000]     5112 ms: Scavenge 7.0 (10.4) -> 7.0 (12.4) MB, 2.4 / 0.0 ms  allocation failure
[10826:0x103000000]     5115 ms: Scavenge 7.5 (12.4) -> 7.5 (16.4) MB, 2.4 / 0.0 ms  allocation failure
[10826:0x103000000]     5122 ms: Scavenge 9.9 (16.4) -> 10.0 (16.9) MB, 4.8 / 0.0 ms  allocation failure
[10826:0x103000000]     5127 ms: Scavenge 10.4 (16.9) -> 10.3 (25.4) MB, 5.5 / 0.0 ms  allocation failure
[10826:0x103000000]     5141 ms: Scavenge 15.8 (25.4) -> 16.0 (25.9) MB, 10.4 / 0.0 ms  allocation failure
[10826:0x103000000]     5154 ms: Scavenge 16.2 (25.9) -> 16.0 (43.4) MB, 12.9 / 0.0 ms  allocation failure
[10826:0x103000000]     5182 ms: Scavenge 27.6 (43.4) -> 28.1 (43.9) MB, 21.2 / 0.0 ms  allocation failure
[10826:0x103000000]     5202 ms: Scavenge 28.1 (43.9) -> 27.6 (63.4) MB, 20.2 / 0.0 ms  allocation failure
1000000
[10826:0x103000000]     5234 ms: Scavenge 43.3 (63.4) -> 44.0 (63.9) MB, 24.7 / 0.0 ms  allocation failure
[10826:0x103000000]     5266 ms: Scavenge 44.1 (63.9) -> 43.3 (79.4) MB, 32.4 / 0.0 ms  allocation failure
[10826:0x103000000]     5293 ms: Scavenge 59.1 (79.4) -> 59.8 (79.9) MB, 22.3 / 0.0 ms  allocation failure
[10826:0x103000000]     5323 ms: Scavenge 59.8 (79.9) -> 59.1 (95.4) MB, 30.0 / 0.0 ms  allocation failure
[10826:0x103000000]     5375 ms: Scavenge 74.8 (95.4) -> 75.5 (95.9) MB, 19.9 / 0.0 ms  allocation failure
[10826:0x103000000]     5407 ms: Scavenge 75.5 (95.9) -> 74.8 (111.4) MB, 30.5 / 0.0 ms  allocation failure
2000000
[10826:0x103000000]     5444 ms: Mark-sweep 82.2 (111.4) -> 82.0 (111.4) MB, 8.6 / 0.0 ms  (+ 54.6 ms in 371 steps since start of marking, biggest step 4.0 ms, walltime since start of marking 177 ms) finalize incremental marking via stack guard GC in old space requested
[10826:0x103000000]     5478 ms: Scavenge 90.3 (111.4) -> 90.7 (118.9) MB, 31.9 / 0.0 ms  allocation failure
[10826:0x103000000]     5505 ms: Scavenge 97.7 (118.9) -> 97.7 (127.4) MB, 25.2 / 0.0 ms  allocation failure
[10826:0x103000000]     5534 ms: Scavenge 106.0 (127.4) -> 106.1 (134.9) MB, 26.6 / 0.0 ms  allocation failure
[10826:0x103000000]     5562 ms: Scavenge 113.1 (134.9) -> 113.0 (143.4) MB, 25.7 / 0.0 ms  allocation failure
3000000
[10826:0x103000000]     5589 ms: Scavenge 121.4 (143.4) -> 121.5 (149.4) MB, 23.8 / 0.0 ms  allocation failure
[10826:0x103000000]     5626 ms: Scavenge 128.4 (149.4) -> 128.4 (158.4) MB, 35.4 / 0.0 ms  allocation failure
[10826:0x103000000]     5656 ms: Scavenge 136.8 (158.4) -> 136.9 (165.4) MB, 27.3 / 0.0 ms  allocation failure
[10826:0x103000000]     5699 ms: Scavenge 143.8 (165.4) -> 143.7 (173.9) MB, 26.4 / 0.0 ms  allocation failure
[10826:0x103000000]     5762 ms: Scavenge 152.3 (173.9) -> 152.3 (180.9) MB, 25.3 / 0.0 ms  allocation failure
4000000
[10826:0x103000000]     5829 ms: Scavenge 159.1 (180.9) -> 159.0 (189.4) MB, 26.8 / 0.0 ms  allocation failure
[10826:0x103000000]     5854 ms: Mark-sweep 161.0 (189.4) -> 160.7 (196.9) MB, 3.6 / 0.0 ms  (+ 108.5 ms in 387 steps since start of marking, biggest step 4.4 ms, walltime since start of marking 228 ms) finalize incremental marking via stack guard GC in old space requested
[10826:0x103000000]     5891 ms: Scavenge 174.4 (196.9) -> 175.1 (198.9) MB, 32.0 / 0.0 ms  allocation failure
[10826:0x103000000]     5923 ms: Scavenge 176.4 (198.9) -> 175.8 (212.9) MB, 31.6 / 0.0 ms  allocation failure
[10826:0x103000000]     5946 ms: Scavenge 190.1 (212.9) -> 190.8 (214.4) MB, 18.6 / 0.0 ms  allocation failure
[10826:0x103000000]     5976 ms: Scavenge 191.5 (214.4) -> 190.8 (229.4) MB, 29.8 / 0.0 ms  allocation failure
5000000
[10826:0x103000000]     6002 ms: Scavenge 205.8 (229.4) -> 206.5 (229.4) MB, 19.4 / 0.0 ms  allocation failure
[10826:0x103000000]     6034 ms: Scavenge 206.5 (229.4) -> 205.8 (244.4) MB, 31.7 / 0.0 ms  allocation failure
[10826:0x103000000]     6059 ms: Scavenge 221.5 (244.4) -> 222.2 (244.9) MB, 20.9 / 0.0 ms  allocation failure
[10826:0x103000000]     6088 ms: Scavenge 222.3 (244.9) -> 221.5 (260.4) MB, 28.4 / 0.0 ms  allocation failure
6000000
[10826:0x103000000]     6114 ms: Scavenge 237.3 (260.4) -> 238.0 (260.9) MB, 22.3 / 0.0 ms  allocation failure
[10826:0x103000000]     6142 ms: Scavenge 238.0 (260.9) -> 237.3 (276.4) MB, 27.5 / 0.0 ms  allocation failure
[10826:0x103000000]     6169 ms: Scavenge 253.0 (276.4) -> 253.7 (276.9) MB, 22.4 / 0.0 ms  allocation failure
[10826:0x103000000]     6203 ms: Scavenge 253.7 (276.9) -> 253.0 (292.4) MB, 33.6 / 0.0 ms  allocation failure
[10826:0x103000000]     6380 ms: Scavenge 268.7 (292.4) -> 269.4 (292.9) MB, 20.6 / 0.0 ms  allocation failure
[10826:0x103000000]     6414 ms: Scavenge 269.5 (292.9) -> 268.7 (308.4) MB, 32.1 / 0.0 ms  allocation failure
[10826:0x103000000]     6446 ms: Mark-sweep 269.9 (308.4) -> 269.9 (308.4) MB, 2.1 / 0.0 ms  (+ 183.4 ms in 270 steps since start of marking, biggest step 5.5 ms, walltime since start of marking 304 ms) finalize incremental marking via stack guard GC in old space requested
7000000
[10826:0x103000000]     6478 ms: Scavenge 284.5 (308.4) -> 285.2 (309.9) MB, 25.2 / 0.0 ms  allocation failure
[10826:0x103000000]     6510 ms: Scavenge 285.6 (309.9) -> 284.9 (324.9) MB, 32.4 / 0.0 ms  allocation failure
[10826:0x103000000]     6538 ms: Scavenge 300.2 (324.9) -> 300.9 (325.9) MB, 22.8 / 0.0 ms  allocation failure
[10826:0x103000000]     6570 ms: Scavenge 300.9 (325.9) -> 300.2 (341.4) MB, 31.8 / 0.0 ms  allocation failure
8000000
[10826:0x103000000]     6595 ms: Scavenge 315.9 (341.4) -> 316.7 (342.4) MB, 20.6 / 0.0 ms  allocation failure
[10826:0x103000000]     6643 ms: Scavenge 316.7 (342.4) -> 316.0 (357.9) MB, 48.2 / 0.0 ms  allocation failure
[10826:0x103000000]     6670 ms: Scavenge 331.7 (357.9) -> 332.4 (358.9) MB, 21.9 / 0.0 ms  allocation failure
[10826:0x103000000]     6703 ms: Scavenge 332.4 (358.9) -> 331.7 (374.4) MB, 32.4 / 0.0 ms  allocation failure
[10826:0x103000000]     6728 ms: Scavenge 347.4 (374.4) -> 348.1 (375.4) MB, 20.7 / 0.0 ms  allocation failure
[10826:0x103000000]     6756 ms: Scavenge 348.1 (375.4) -> 347.4 (390.9) MB, 27.9 / 0.0 ms  allocation failure
9000000
[10826:0x103000000]     6986 ms: Mark-sweep 350.9 (390.9) -> 350.8 (390.9) MB, 2.3 / 0.0 ms  (+ 225.8 ms in 55 steps since start of marking, biggest step 6.0 ms, walltime since start of marking 230 ms) finalize incremental marking via stack guard GC in old space requested
[10826:0x103000000]     7020 ms: Scavenge 363.1 (390.9) -> 363.7 (392.4) MB, 30.2 / 0.0 ms  allocation failure
[10826:0x103000000]     7055 ms: Scavenge 366.6 (392.4) -> 366.1 (404.9) MB, 33.7 / 0.0 ms  allocation failure
[10826:0x103000000]     7080 ms: Scavenge 378.9 (404.9) -> 379.3 (407.9) MB, 21.7 / 0.0 ms  allocation failure
[10826:0x103000000]     7106 ms: Scavenge 381.7 (407.9) -> 381.2 (420.9) MB, 25.2 / 0.0 ms  allocation failure
[10826:0x103000000]     7342 ms: Mark-sweep 384.8 (420.9) -> 384.7 (422.9) MB, 3.3 / 0.0 ms  (+ 231.9 ms in 58 steps since start of marking, biggest step 5.5 ms, walltime since start of marking 237 ms) finalize incremental marking via stack guard GC in old space requested
done
385.70 MB
[10826:0x103000000]     7384 ms: Mark-sweep 385.7 (422.9) -> 4.2 (40.9) MB, 39.4 / 0.0 ms  (+ 0.0 ms in 1 steps since start of marking, biggest step 0.0 ms, walltime since start of marking 39 ms) finalize incremental marking via task GC in old space requested
4.22 MB
4.23 MB
4.23 MB
4.23 MB
starting
0
[10826:0x103000000]    12380 ms: Scavenge 20.0 (40.9) -> 20.7 (40.9) MB, 19.4 / 0.0 ms  allocation failure
[10826:0x103000000]    12406 ms: Scavenge 20.7 (40.9) -> 20.0 (55.4) MB, 26.7 / 0.0 ms  allocation failure
[10826:0x103000000]    12430 ms: Scavenge 35.7 (55.4) -> 36.4 (55.9) MB, 19.5 / 0.0 ms  allocation failure
[10826:0x103000000]    12457 ms: Scavenge 36.4 (55.9) -> 35.7 (71.4) MB, 27.3 / 0.0 ms  allocation failure
1000000
[10826:0x103000000]    12484 ms: Scavenge 51.4 (71.4) -> 52.1 (71.9) MB, 22.6 / 0.0 ms  allocation failure
[10826:0x103000000]    12513 ms: Scavenge 52.2 (71.9) -> 51.4 (87.4) MB, 28.2 / 0.0 ms  allocation failure
[10826:0x103000000]    12551 ms: Scavenge 67.2 (87.4) -> 67.9 (87.9) MB, 21.9 / 0.0 ms  allocation failure
[10826:0x103000000]    12578 ms: Scavenge 67.9 (87.9) -> 67.2 (103.4) MB, 26.9 / 0.0 ms  allocation failure
2000000
[10826:0x103000000]    12646 ms: Mark-sweep 81.7 (103.4) -> 81.7 (103.4) MB, 30.1 / 0.0 ms  (+ 46.7 ms in 311 steps since start of marking, biggest step 5.2 ms, walltime since start of marking 189 ms) finalize incremental marking via stack guard GC in old space requested
[10826:0x103000000]    12680 ms: Scavenge 82.9 (103.4) -> 82.9 (118.4) MB, 31.8 / 0.0 ms  allocation failure
[10826:0x103000000]    12712 ms: Scavenge 97.5 (118.4) -> 98.1 (119.9) MB, 20.8 / 0.0 ms  allocation failure
[10826:0x103000000]    12738 ms: Scavenge 98.6 (119.9) -> 98.0 (134.9) MB, 26.8 / 0.0 ms  allocation failure
[10826:0x103000000]    12762 ms: Scavenge 113.1 (134.9) -> 113.8 (135.9) MB, 20.1 / 0.0 ms  allocation failure
[10826:0x103000000]    12789 ms: Scavenge 113.9 (135.9) -> 113.1 (151.4) MB, 26.8 / 0.0 ms  allocation failure
3000000
[10826:0x103000000]    12813 ms: Scavenge 128.9 (151.4) -> 129.6 (151.9) MB, 20.6 / 0.0 ms  allocation failure
[10826:0x103000000]    12839 ms: Scavenge 129.6 (151.9) -> 128.9 (166.4) MB, 25.4 / 0.0 ms  allocation failure
[10826:0x103000000]    12913 ms: Scavenge 144.6 (166.4) -> 145.3 (166.9) MB, 20.9 / 0.0 ms  allocation failure
[10826:0x103000000]    12944 ms: Scavenge 145.3 (166.9) -> 144.6 (182.4) MB, 29.2 / 0.0 ms  allocation failure
[10826:0x103000000]    12997 ms: Mark-sweep 151.5 (182.4) -> 151.5 (182.4) MB, 3.9 / 0.0 ms  (+ 98.2 ms in 363 steps since start of marking, biggest step 5.0 ms, walltime since start of marking 208 ms) finalize incremental marking via stack guard GC in old space requested
4000000
[10826:0x103000000]    13028 ms: Scavenge 160.3 (182.4) -> 160.8 (189.4) MB, 28.7 / 0.0 ms  allocation failure
[10826:0x103000000]    13054 ms: Scavenge 167.3 (189.4) -> 167.2 (198.4) MB, 24.2 / 0.0 ms  allocation failure
[10826:0x103000000]    13079 ms: Scavenge 176.1 (198.4) -> 176.2 (205.4) MB, 22.3 / 0.0 ms  allocation failure
[10826:0x103000000]    13104 ms: Scavenge 182.6 (205.4) -> 182.4 (214.9) MB, 23.8 / 0.0 ms  allocation failure
[10826:0x103000000]    13129 ms: Scavenge 191.5 (214.9) -> 191.6 (221.4) MB, 23.1 / 0.0 ms  allocation failure
5000000
[10826:0x103000000]    13155 ms: Scavenge 197.9 (221.4) -> 197.7 (230.9) MB, 24.9 / 0.0 ms  allocation failure
[10826:0x103000000]    13181 ms: Scavenge 206.9 (230.9) -> 207.1 (237.4) MB, 24.0 / 0.0 ms  allocation failure
[10826:0x103000000]    13206 ms: Scavenge 213.2 (237.4) -> 213.0 (246.9) MB, 23.2 / 0.0 ms  allocation failure
[10826:0x103000000]    13232 ms: Scavenge 222.4 (246.9) -> 222.5 (253.4) MB, 23.0 / 0.0 ms  allocation failure
[10826:0x103000000]    13258 ms: Scavenge 228.5 (253.4) -> 228.3 (262.9) MB, 24.9 / 0.0 ms  allocation failure
6000000
[10826:0x103000000]    13316 ms: Scavenge 237.8 (262.9) -> 238.0 (267.9) MB, 24.6 / 0.0 ms  allocation failure
[10826:0x103000000]    13381 ms: Scavenge 243.8 (267.9) -> 243.6 (277.9) MB, 25.9 / 0.0 ms  allocation failure
[10826:0x103000000]    13480 ms: Mark-sweep 251.5 (277.9) -> 251.2 (282.9) MB, 5.3 / 0.0 ms  (+ 159.5 ms in 371 steps since start of marking, biggest step 4.1 ms, walltime since start of marking 248 ms) finalize incremental marking via stack guard GC in old space requested
[10826:0x103000000]    13517 ms: Scavenge 259.1 (282.9) -> 259.4 (290.4) MB, 34.7 / 0.0 ms  allocation failure
[10826:0x103000000]    13545 ms: Scavenge 266.9 (290.4) -> 266.9 (298.4) MB, 23.6 / 0.0 ms  allocation failure
7000000
[10826:0x103000000]    13571 ms: Scavenge 274.8 (298.4) -> 274.8 (306.4) MB, 24.2 / 0.0 ms  allocation failure
[10826:0x103000000]    13597 ms: Scavenge 282.3 (306.4) -> 282.3 (313.9) MB, 24.2 / 0.0 ms  allocation failure
[10826:0x103000000]    13636 ms: Scavenge 290.1 (313.9) -> 290.1 (321.9) MB, 36.8 / 0.0 ms  allocation failure
[10826:0x103000000]    13662 ms: Scavenge 297.7 (321.9) -> 297.7 (329.9) MB, 24.3 / 0.0 ms  allocation failure
[10826:0x103000000]    13687 ms: Scavenge 305.5 (329.9) -> 305.5 (337.4) MB, 23.7 / 0.0 ms  allocation failure
8000000
[10826:0x103000000]    13717 ms: Scavenge 313.0 (337.4) -> 313.0 (345.4) MB, 27.6 / 0.0 ms  allocation failure
[10826:0x103000000]    13746 ms: Scavenge 320.9 (345.4) -> 320.9 (352.9) MB, 25.9 / 0.0 ms  allocation failure
[10826:0x103000000]    13773 ms: Scavenge 328.4 (352.9) -> 328.4 (360.9) MB, 24.9 / 0.0 ms  allocation failure
[10826:0x103000000]    13798 ms: Scavenge 336.2 (360.9) -> 336.2 (368.9) MB, 23.5 / 0.0 ms  allocation failure
[10826:0x103000000]    14033 ms: Mark-sweep 339.4 (368.9) -> 339.0 (376.9) MB, 4.0 / 0.0 ms  (+ 229.2 ms in 50 steps since start of marking, biggest step 5.7 ms, walltime since start of marking 234 ms) finalize incremental marking via stack guard GC in old space requested
9000000
[10826:0x103000000]    14072 ms: Scavenge 351.6 (376.9) -> 352.2 (379.9) MB, 35.7 / 0.0 ms  allocation failure
[10826:0x103000000]    14101 ms: Scavenge 354.7 (379.9) -> 354.3 (392.9) MB, 28.0 / 0.0 ms  allocation failure
[10826:0x103000000]    14125 ms: Scavenge 367.3 (392.9) -> 367.8 (395.9) MB, 21.4 / 0.0 ms  allocation failure
[10826:0x103000000]    14154 ms: Scavenge 369.9 (395.9) -> 369.3 (409.4) MB, 28.1 / 0.0 ms  allocation failure
[10826:0x103000000]    14178 ms: Scavenge 383.0 (409.4) -> 383.5 (411.9) MB, 20.5 / 0.0 ms  allocation failure
[10826:0x103000000]    14311 ms: Scavenge 385.0 (411.9) -> 384.4 (425.4) MB, 30.4 / 0.0 ms  allocation failure
done
385.77 MB
[10826:0x103000000]    14455 ms: Mark-sweep 385.8 (425.4) -> 382.9 (425.4) MB, 1.2 / 0.0 ms  (+ 244.2 ms in 103 steps since start of marking, biggest step 6.2 ms, walltime since start of marking 278 ms) finalize incremental marking via task GC in old space requested
382.93 MB
[10826:0x103000000]    15413 ms: Mark-sweep 382.9 (425.4) -> 4.2 (41.9) MB, 0.8 / 0.0 ms  (+ 2.8 ms in 4 steps since start of marking, biggest step 1.1 ms, walltime since start of marking 4 ms) finalize incremental marking via task GC in old space requested
4.22 MB
4.23 MB

在这种情况下,清理循环并没有真正回收任何内存,我们必须等待完整的标记-清除才能释放所有内存。
这有意义吗?
总之,我想补充一点:
1. 真实代码通常不会在一个tick中进行1000万次字符串连接。 2. V8的垃圾回收算法通常会做正确的事情,以在防止进程耗尽内存和防止自身不必要地减慢进程之间取得平衡。 3. 如果您遇到垃圾回收问题,则需要进行一些分析,并且解决方案通常涉及重构代码以在一个tick中执行更少的工作。它可能会更加复杂,但是深入了解这个问题可能与本问题无关。

6

MDN web文档详细介绍了2种JavaScript垃圾回收算法。

  1. 引用计数算法
  2. 标记清除算法

您提到的是第一种,即引用计数算法。当变量没有其他指向它们的引用时,该算法会清除对象。因此,当函数返回时,所有不可访问的对象都被清除并释放内存。

如果对象彼此引用,创建了一个引用循环,则引用计数算法无法清除不再需要的对象。即使一个对象对自身有引用但没有其他引用,也永远不会被释放,因为它有一个引用,这些孤立的对象会持续存在于内存中。只有IE6IE7实现了这种垃圾回收算法。

所有现代浏览器都使用标记清除算法的垃圾收集器,在特定间隔或在认为有必要释放所有未使用的内存分配时扫描整个内存。来自MDN web文档

该算法假设知道一组称为根的对象(在JavaScript中,根是全局对象)。 定期,垃圾回收器将从这些根开始,查找从这些根引用的所有对象,然后查找从这些引用的所有对象,等等。从根开始,垃圾回收器因此会找到所有可达对象并收集所有不可达对象。

这就是为什么当函数立即返回时,您看到内存使用情况不会下降,标记清除垃圾回收器不会在每次函数返回后执行,而是定期执行。因此,当循环执行并返回函数时,您会看到内存使用量增加,但它会保持高水平,直到GC启动并清除垃圾,内存使用量才会下降。


请问您能提供一下函数执行完毕后GC触发的参考文献吗?我错过了这个。 - Munim Munna
函数执行后,GC不会立即触发。通常,“标记清除法”会在函数退出后进行标记。 - SwiftMango
我在想为什么几秒钟后垃圾回收器没有启动。我还尝试通过调用 gc() 来手动强制进行垃圾回收,但结果相同。 - SwiftMango
js-gc npm页面支持平台部分中指出,gc()函数并不被所有地方支持。 - Munim Munna
请查看我的答案更新,添加了两种算法的详细信息。 - Munim Munna
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接