Elasticsearch嵌套JSON打平时创建Dictionary出现OutOfMemoryException
问题原因分析
- 一次性全量加载数据:你调用
GetDocumentsFromES()时直接将5万条记录全部加载到List<object>中,所有原始文档对象在转换前就占用了大量内存空间。 - 惰性求值导致对象累积:
Select是惰性操作,只要你没有逐批消费结果,所有转换过程中生成的JObject、Dictionary、字符串实例都会被持有引用,不会在单次迭代结束后被GC回收,内存占用会持续走高直到迭代完成。 - 节点筛选逻辑错误:
jObject.Descendants()会返回所有类型的JToken(包括JProperty、JObject、JValue等),你的筛选条件!j.Children().Any()会同时命中JValue和部分无嵌套的JProperty,导致遍历的节点数远超实际需要,大量重复的临时对象和字符串被创建,瞬间占用大量内存,还可能引发字典重复Key的隐性问题。 - 大对象堆碎片化:每次生成
JObject、大字典时产生的大对象会进入大对象堆(LOH),LOH默认不会自动压缩,内存碎片化后即使有剩余空闲空间也无法分配新的连续内存块,最终抛出OutOfMemoryException。
修复方案
- 优化数据加载逻辑:将
GetDocumentsFromES的返回值从List<object>改为流式的IEnumerable<object>,使用ES的Scroll/ScrollAsync接口逐批拉取数据,处理完一批就释放一批内存,不要一次性全量加载所有文档。 - 修正节点筛选逻辑:只筛选
JValue类型的叶子节点,大幅减少遍历的节点数量:
// 替换原有字典生成逻辑 var dict = jObject.Descendants() .OfType<JValue>() .ToDictionary(k => k.Path, v => v.ToString());
- 避免全量结果驻留内存:不要将5万条转换后的
JObject全部保存在内存中,每处理1000~2000条就将结果落地(写文件、存数据库、发往下游),然后清空临时集合,主动触发GC回收无用对象。 - 减少中间对象生成:如果后续需要将转换结果序列化输出,可以直接遍历JToken生成JSON字符串,跳过
Dictionary和JObject.FromObject的中间转换步骤,进一步降低内存占用。
内容的提问来源于stack exchange,提问作者StNickolas
相关产品推荐
相关产品推荐

