heap out of memory
项目中使用了 TypeScript + Vite + Vue,一次手贱升级了 Vite 版本,部署到预发布环境时打包出错了:
Jenkins 输出日志:
11:07:27 <--- Last few GCs --->
11:07:27
11:07:27 [15088:0x6437390] 103233 ms: Mark-sweep (reduce) 2046.0 (2082.6) -> 2045.1 (2082.9) MB, 3591.7 / 0.0 ms (average mu = 0.155, current mu = 0.011) allocation failure scavenge might not succeed
11:07:27 [15088:0x6437390] 106853 ms: Mark-sweep (reduce) 2046.6 (2083.4) -> 2045.5 (2083.6) MB, 3610.4 / 0.0 ms (average mu = 0.084, current mu = 0.003) allocation failure scavenge might not succeed
11:07:27
11:07:27
11:07:27 <--- JS stacktrace --->
11:07:27
11:07:27 FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
11:07:27 1: 0xb00e10 node::Abort() [node]
11:07:27 2: 0xa1823b node::FatalError(char const*, char const*) [node]
11:07:27 3: 0xcee09e v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [node]
11:07:27 4: 0xcee417 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [node]
11:07:27 5: 0xea65d5 [node]
11:07:27 6: 0xeb5cad v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node]
11:07:27 7: 0xeb89ae v8::internal::Heap::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [node]
11:07:27 8: 0xe79dda v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationType, v8::internal::AllocationOrigin) [node]
11:07:27 9: 0x11f33d6 v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [node]
11:07:27 10: 0x15e7cf9 [node]
11:07:27 sh: line 1: 15088 Aborted vue-tsc --noEmit
看起来是内存溢出了,于是在 vite 的 issue 里查一下发现了内存溢出的话题
讨论的结果是,通过下面这个指令来放宽 linux 内存的使用限制:
cross-env NODE_OPTIONS=--max-old-space-size=4096 vite build
问题可能在于低版本的 Node 可能有 GC 上的问题
修改打包脚本,重新部署,发现还是有问题;
原以为是 vite build 阶段发生的问题,仔细看 output log 发现是在 ts 编译阶段 vue-tsc --noEmit
发生的错误;
所以放宽内存限制的指令得放到 vue-tsc --noEmit
前面
cross-env NODE_OPTIONS=--max-old-space-size=4096 vue-tsc --noEmit && vite build
在 vue-tsc 的 github 上也发现了这个话题:
关于 Node 的 max-old-space-size
参数可以参考下面文章:
按照我对原文的理解,我翻译一下:
“old space”是 V8 所管理的堆中最大和最可配置的部分,也是垃圾收集(GC)作用的地方,--max-old-space-size 标志控制其最大大小。随着内存消耗接近极限,V8 将花费更多时间在垃圾收集上,以释放未使用的内存。
如果堆内存消耗(即 GC 无法释放的活对象)超过了限制,V8 就会让你的进程崩溃(因为没有其他办法),所以你不要把它设得太低。当然,如果设置过高,那么 V8 允许的额外堆使用量可能会导致整个系统内存耗尽(因为没有其他选择,所以会 swap 或杀死随机进程)
总之,在拥有 2GB 内存的机器上,我可能会将 --max-old-space-size 设置为 1.5GB 左右,以便为其他用途留出一些内存,避免 swap。
关于 max-old-space-size 选项,在 Node 官方文档有记录。
对于 2GB 的机器,您可能应该使用:
NODE_OPTIONS=--max-old-space-size=1536
'new space' 与 'old space' 到底是什么?
这篇文章回答得很好
确定内存使用量
您可以使用 free -m 查看 Linux 机器上的可用内存。请注意,您可以考虑可用的 free 和 buffers/cache 内存的总和,因为 buffers/cache 可以立即丢弃这些缓冲区一种使用未使用内存的好方法)。
Node 文档中还提到:
On a machine with 2GB of memory, consider setting this to 1536 (1.5GB)
因此考虑到基本操作系统所需的内存容量不会有太大变化,因此您可以在 4GB 机器上愉快地定为 3.5GB 左右。
要注意默认值并查看更改的效果:
默认为 2GB:
$ node
> v8.getHeapStatistics()
{
....
heap_size_limit: 2197815296,
}
2197815296 是 2GB 字节。
当设置为8GB时,可以看到 heap_size_limit 变化:
$ NODE_OPTIONS=--max_old_space_size=8192 node
Welcome to Node.js v14.17.4.
Type ".help" for more information.
> v8.getHeapStatistics()
{
...
heap_size_limit: 8640266240,
...
}
关于 V8 的 old space 是什么,可以参考下面文章介绍: