如何减少V8中频繁的Scavenge GC?
如何将V8中对象直接分配到老生代以减少Scavenge GC触发?
问题场景
我编写了一段创建百万级对象的测试代码,运行时频繁触发新生代的Scavenge GC,对性能造成了明显影响。从GC追踪日志可以看到多次Scavenge分配失败的记录,现在希望让新创建的Obj对象直接分配到老生代,从而减少Scavenge GC的触发次数。
测试代码:
function Obj () { } function genObj () { return new Obj() } globalThis.objPool = new Array( 1_000_000 ) function test () { globalThis.objPool.length = 0 for ( var i = 0; i < 1_000_000; i++ ) { globalThis.objPool.push( genObj() ) } } test()
d8 GC追踪结果:
d8 --trace-gc test.js [35164:000001B1DE2EBBA0] 120 ms: Scavenge 5.0 (5.4) -> 1.0 (2.6) MB, 5.74 / 0.00 ms (average mu = 1.000, current mu = 1.000) allocation failure; [35164:000001B1DE2EBBA0] 134 ms: Scavenge 2.2 (3.3) -> 1.8 (3.7) MB, 12.10 / 0.00 ms (average mu = 1.000, current mu = 1.000) allocation failure; [35164:000001B1DE2EBBA0] 155 ms: Scavenge 2.6 (4.1) -> 2.6 (6.6) MB, 18.12 / 0.00 ms (average mu = 1.000, current mu = 1.000) allocation failure; [35164:000001B1DE2EBBA0] 189 ms: Scavenge 5.7 (8.3) -> 5.2 (7.9) MB, 28.67 / 0.00 ms (average mu = 1.000, current mu = 1.000) allocation failure; [35164:000001B1DE2EBBA0] 231 ms: Scavenge 6.9 (9.3) -> 6.8 (15.1) MB, 36.22 / 0.00 ms (average mu = 1.000, current mu = 1.000) allocation failure; [35164:000001B1DE2EBBA0] 296 ms: Scavenge 12.6 (17.3) -> 12.9 (17.3) MB, 55.07 / 0.00 ms (average mu = 1.000, current mu = 1.000) allocation failure; [35164:000001B1DE2EBBA0] 356 ms: Scavenge 12.9 (17.3) -> 12.6 (29.3) MB, 58.69 / 0.00 ms (average mu = 1.000, current mu = 1.000) allocation failure; [35164:000001B1DE2EBBA0] 426 ms: Mark-Compact 17.7 (32.6) -> 14.1 (31.2) MB, 14.08 / 0.01 ms (+ 13.4 ms in 30 steps since start of marking, biggest step 0.8 ms, walltime since start of marking 61 ms) (average mu = 1.000, current mu = 1.000) finalize incremental marking via stack guard; GC in old space requested
解决方案
强制直接分配到老生代
最直接的方式是使用d8的启动参数--force-old-space-allocation,修改运行命令为:d8 --trace-gc --force-old-space-allocation test.js这个参数会强制所有新创建的对象直接进入老生代内存,彻底避免新生代的Scavenge GC触发。对比修改后的GC日志,你会发现不再出现Scavenge相关记录,只会有老生代的Mark-Compact等GC操作。
调整新生代内存大小减少Scavenge频率
如果你不想全局强制所有对象进老生代,可以通过--min-semi-space-size和--max-semi-space-size参数增大新生代空间(单位为MB),减少Scavenge触发次数:d8 --trace-gc --min-semi-space-size=64 --max-semi-space-size=64 test.js这种方式不会改变对象分配逻辑,但更大的新生代空间能容纳更多对象,降低Scavenge的触发频率。
需要注意的是,--force-old-space-allocation属于V8的调试/测试参数,生产环境使用需谨慎——老生代GC的开销通常比新生代Scavenge更大,只有当你明确知道对象会长期存活(比如示例中对象池里的对象),这种方式才会带来性能收益。
内容的提问来源于stack exchange,提问作者printf
相关产品推荐
相关产品推荐

