NodeJS循环读取大量JSON文件时堆内存耗尽问题排查
为什么会出现JavaScript heap out of memory错误?
你的代码里最核心的问题是用require()来读取JSON数据文件,而require的设计初衷是加载Node.js模块(包括JSON模块),它会自动缓存所有加载过的模块——也就是说,你读取的每一个6MB JSON文件,都会被永久存在内存的缓存里,不会被垃圾回收(GC)清除。
220个6MB的文件总大小是1320MB,再加上处理过程中产生的对象、V8引擎本身的内存开销,很容易就超过了Node.js默认的堆内存限制(通常在1.4GB左右),这就是为什么运行一段时间后会触发内存不足的报错。
另外,虽然你提到处理后的数据会快速提交到服务,但如果处理逻辑中不小心保留了对records或record对象的全局引用(比如存在某个不会被清空的数组、对象里),也会加剧内存泄漏,但最主要的元凶还是require的缓存机制。
当前的JSON读取方式正确吗?
不正确。require不适合用来读取大量一次性使用的数据文件:
- 它的缓存机制会导致内存无法释放,对于批量处理场景非常不友好;
- 同步读取的方式(
readdirSync+require)会阻塞Event Loop,影响程序的响应性(虽然你的场景是批量处理,可能暂时不关心,但内存问题是硬伤)。
解决方法
1. 改用fs读取+手动JSON.parse(替代require)
放弃require,改用fs.readFileSync(同步场景)或fs.readFile(异步场景)读取文件内容,再手动解析JSON。这样读取的内容不会被缓存,处理完后如果没有引用,就会被GC回收:
const fs = require('fs'); const path = require('path'); const dirPath = '/500gb/json_files/gnip_30_p2'; let fileList = fs.readdirSync(dirPath); fileList = fileList.reverse(); let totalErrors = 0; let totalFiles = 0; for (let file of fileList) { totalFiles++; const filePath = path.join(dirPath, file); // 读取文件内容并解析JSON const fileContent = fs.readFileSync(filePath, 'utf8'); const records = JSON.parse(fileContent); const results = records.results; if (results) { for (let record of results) { // .. 你的处理逻辑 .. } // 手动解除引用,帮助GC回收(可选,V8通常会自动处理,但显式做更稳妥) records = null; results = null; } }
2. 用流式解析进一步降低内存占用(推荐)
如果单个JSON文件结构支持(比如是数组格式的JSON),可以用流式解析库(比如JSONStream),不需要把整个6MB的JSON加载到内存,而是逐段读取解析,内存占用会大幅降低:
首先安装依赖:
npm install jsonstream
然后修改代码:
const fs = require('fs'); const path = require('path'); const JSONStream = require('JSONStream'); const dirPath = '/500gb/json_files/gnip_30_p2'; let fileList = fs.readdirSync(dirPath); fileList = fileList.reverse(); let totalErrors = 0; let totalFiles = 0; // 用异步循环避免阻塞Event Loop async function processFiles() { for (let file of fileList) { totalFiles++; const filePath = path.join(dirPath, file); await new Promise((resolve, reject) => { fs.createReadStream(filePath, 'utf8') .pipe(JSONStream.parse('results.*')) // 解析results数组里的每一项 .on('data', (record) => { // .. 你的处理逻辑 .. }) .on('error', (err) => { totalErrors++; console.error(`处理文件${file}出错:`, err); reject(err); }) .on('end', resolve); }); } } processFiles().then(() => { console.log(`所有文件处理完成,总文件数: ${totalFiles},错误数: ${totalErrors}`); }).catch((err) => { console.error('处理流程出错:', err); });
3. 临时方案:增加Node.js堆内存限制
如果你暂时不想修改代码,可以通过启动参数增加堆内存,比如设置为2GB:
node --max-old-space-size=2048 your-script.js
但这只是治标不治本的方法,随着文件数量增加,还是会遇到内存问题,优先推荐前两种方法。
内容的提问来源于stack exchange,提问作者Suhail Gupta

