You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js v20下Angular Jest测试内存泄漏及手动GC调用咨询

问题描述

将Node.js升级至v20后,Angular项目执行Jest测试时出现以下错误:

FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory

同时测试耗时从原先的15分钟增至2小时以上。项目包含数千个Angular组件测试,在Node.js v16中运行无此问题。使用6线程并行测试并添加--logHeapUsage参数后,发现新版本存在明显内存泄漏:堆内存初始为310MB,持续增长至4GB甚至更高。

执行命令:

npx jest --logHeapUsage

相关错误日志:

<--- Last few GCs --->

[11248:000002CB7489B580]   762926 ms: Mark-Compact (reduce) 4031.9 (4143.6) -> 4031.7 (4143.6) MB, 5514.89 / 0.00 ms  (average mu = 0.169, current mu = 0.018) allocation failure; scavenge might not succeed
[11248:000002CB7489B580]   768007 ms: Mark-Compact (reduce) 4032.7 (4143.6) -> 4032.6 (4144.6) MB, 5078.96 / 0.00 ms  (average mu = 0.090, current mu = 0.001) allocation failure; scavenge might not succeed


<--- JS stacktrace --->


 Test suite failed to run

    A jest worker process (pid=11248) was terminated by another process: signal=SIGTERM, exitCode=null. Operating system logs may contain more information on why this occurred.

      at ChildProcessWorker._onExit (../../../node_modules/jest-worker/build/workers/ChildProcessWorker.js:370:23)

线程会在内存突破4GB时崩溃,且内存堆越大测试速度越慢。虽然添加max-old-space-size参数可避免内存溢出,但测试耗时仍超2小时,无法接受。希望在测试执行过程中调用Node.js垃圾回收,或寻求其他缓解内存泄漏的方案。

解决方案

一、手动触发Node.js垃圾回收

Node.js默认禁用手动GC,需启动时添加--expose-gc参数才能调用global.gc()。

  1. 修改Jest启动命令:
node --expose-gc node_modules/jest/bin/jest.js --logHeapUsage

也可在package.json的scripts中配置:

"scripts": {
  "test": "node --expose-gc node_modules/jest/bin/jest.js"
}
  1. 在测试中添加GC触发逻辑:
    可在每个测试套件完成后触发GC,比如在afterAll或afterEach中:
afterAll(() => {
  if (typeof global.gc === 'function') {
    global.gc();
  }
});

或通过全局测试钩子统一配置,在jest.config.js中设置:

// jest.config.js
module.exports = {
  setupFilesAfterEnv: ['./src/test-setup.ts'],
};

然后在test-setup.ts中写入:

afterAll(() => {
  if (typeof global.gc === 'function') {
    global.gc();
  }
});

注意:手动GC会增加少量单次测试耗时,但能有效控制内存增长,整体可能缩短总耗时。

二、调整Jest并行配置

内存泄漏在多线程环境下会被放大,可通过以下参数优化:

  1. 减少并行工作线程数:
    通过--maxWorkers指定更少线程,比如从6降到3:
npx jest --maxWorkers=3 --logHeapUsage

或在jest.config.js中配置:

module.exports = {
  maxWorkers: 3,
};
  1. 启用单线程运行:
    使用--runInBand参数强制单线程执行,避免多线程内存叠加:
npx jest --runInBand --logHeapUsage
  1. 配置工作线程内存阈值(Jest 29+支持):
    设置workerIdleMemoryLimit,当工作线程内存超过阈值时自动重启:
module.exports = {
  workerIdleMemoryLimit: '2GB', // 内存超过2GB时重启线程
};

三、排查内存泄漏根源

1. 使用Node.js内存分析工具

添加--inspect-brk参数启动Jest,通过Chrome DevTools分析内存快照:

node --inspect-brk node_modules/jest/bin/jest.js --runInBand

打开Chrome访问chrome://inspect,连接到Node.js进程,录制多次测试后的内存快照,对比找出未释放的对象(如Angular组件实例、未取消的RxJS订阅)。

2. 检查Angular测试常见泄漏点

  • 未取消的RxJS订阅:使用takeUntil配合组件销毁信号,或在测试中手动取消订阅。
  • 未清理的DOM元素:测试结束后调用component.destroy()和fixture.destroy()。
  • 全局状态污染:在beforeEach中重置服务单例的状态。

示例:修复未取消的订阅

import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';

@Component({...})
export class MyComponent {
  private destroy$ = new Subject<void>();

  ngOnInit() {
    this.dataService.getData()
      .pipe(takeUntil(this.destroy$))
      .subscribe(data => { /* ... */ });
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

测试中确保组件销毁:

afterEach(() => {
  fixture.destroy();
});

四、临时调整Node.js参数

除max-old-space-size外,可尝试以下参数优化内存管理:

  • --optimize-for-size:优先优化内存占用,牺牲部分性能。
  • --no-concurrent-mark-sweep:禁用并发标记清除,减少内存碎片。

组合使用示例:

node --expose-gc --max-old-space-size=8192 --optimize-for-size node_modules/jest/bin/jest.js

内容的提问来源于stack exchange,提问作者The Fabio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 05:40:37