“FATAL ERROR: … JavaScript heap out of memory” 最常见的建议是 --max-old-space-size=4096。这篇在本机把上限调得很小来复现它,看默认上限怎么来、调大上限实际改变了什么、Buffer 为什么不算在内,以及一次 JSON.parse 大文件会占掉多少堆。
本机物理内存 16 GiB 默认:heap_size_limit = 4192 MB --max-old-space-size=64:heap_size_limit = 160 MB --max-old-space-size=256:heap_size_limit = 352 MB --max-old-space-size=1024:heap_size_limit = 1120 MB --max-old-space-size=4096:heap_size_limit = 4192 MB --max-old-space-size=16384:heap_size_limit = 16480 MB
由物理内存推出;--max-old-space-size 的设置值再加 96 MB 就是报告的 heap_size_limit。
node --max-old-space-size=64 leak.js(退出码 -6):
已放入 100000 个对象,堆已用 18 MB
已放入 200000 个对象,堆已用 30 MB
已放入 300000 个对象,堆已用 46 MB
已放入 400000 个对象,堆已用 63 MB
已放入 500000 个对象,堆已用 62 MB
<--- Last few GCs --->
[pid:0x…] 179 ms: Mark-Compact 62.9 (65.8) -> 62.9 (65.6) MB, pooled: 1.0 MB, 24.76 / 0.00 ms (average mu = 0.056, current mu = 0.000) allocation failure; GC in old
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
----- Native stack trace -----Last few GCs 里回收前后几乎没变说明回收不掉;最后一行是 Ineffective mark-compacts near heap limit。
--max-old-space-size=32 : 撞上之前最后一次打印是 200000 个对象(退出码 -6) --max-old-space-size=64 : 撞上之前最后一次打印是 500000 个对象(退出码 -6) --max-old-space-size=128 : 撞上之前最后一次打印是 1000000 个对象(退出码 -6) --max-old-space-size=256 : 撞上之前最后一次打印是 2100000 个对象(退出码 -6) --max-old-space-size=512 : 撞上之前最后一次打印是 4400000 个对象(退出码 -6)
能放的对象数大约随上限成正比增长,只是推迟,泄漏还在。
Buffer(退出码 0):分配了 300 MB 的 Buffer:heapUsed = 3 MB,arrayBuffers = 300 MB,rss = 313 MB 普通数组(退出码 -6):FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
Buffer 和 TypedArray 的数据在堆外。
big.json 大小 44 MB,400000 条记录
--max-old-space-size=64 (退出码 -6):FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
--max-old-space-size=128 (退出码 0):读文件 36 ms,JSON.parse 228 ms,400000 条,heapUsed = 92 MB
--max-old-space-size=256 (退出码 0):读文件 13 ms,JSON.parse 217 ms,400000 条,heapUsed = 92 MB
--max-old-space-size=512 (退出码 0):读文件 13 ms,JSON.parse 224 ms,400000 条,heapUsed = 92 MB
流式处理,--max-old-space-size=64(退出码 0):流式扫一遍(只数记录之间的分隔 },{ ,不解析成对象)共 399999 个分隔(记录数减 1),34 ms,heapUsed = 16 MBJSON.parse 之后堆里的占用约是文件大小的两倍;只需要扫描时用流式处理。