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

使用JsonSerializer.DeserializeAsyncEnumerable反序列化1GB文件为何占用2GB+内存?

解决.NET 8中JsonSerializer.DeserializeAsyncEnumerable内存占用过高的问题

1. 确认JSON结构与枚举方式匹配

DeserializeAsyncEnumerable仅针对顶层JSON数组做流式处理,如果你的JSON是行分隔格式(JSON Lines,每行一个对象)或外层有包装对象,它会被迫加载整个文件到内存。

  • 正确的顶层数组格式示例:
    [
      {"Id":1,"Name":"Alice"},
      {"Id":2,"Name":"Bob"},
      ...
    ]
    
  • 如果是JSON Lines格式,需改用逐行读取+单独反序列化:
    await using var stream = File.OpenRead("large.json");
    using var reader = new StreamReader(stream, leaveOpen: true);
    string? line;
    while ((line = await reader.ReadLineAsync()) != null)
    {
        var person = JsonSerializer.Deserialize<Person>(line);
        // 处理person,处理后立即释放引用
        ProcessPerson(person);
        person = null; // 帮助GC标记回收
    }
    

2. 优化JsonSerializerOptions配置

默认配置可能存在不必要的内存开销,调整以下选项:

var options = new JsonSerializerOptions
{
    // 禁用不必要的元数据保留
    IncludeFields = false,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    // 减少序列化/反序列化时的内存分配
    PropertyNameCaseInsensitive = true,
    AllowTrailingCommas = true,
    ReferenceHandler = ReferenceHandler.IgnoreCycles // 避免循环引用导致的内存泄漏
};

使用该配置调用DeserializeAsyncEnumerable:

await using var stream = File.OpenRead("large.json");
await foreach (var person in JsonSerializer.DeserializeAsyncEnumerable<Person>(stream, options))
{
    ProcessPerson(person);
    // 不要将person存入集合,处理后立即丢弃引用
}

3. 避免枚举时缓存所有对象

如果你的基准测试代码中存在将所有Person对象添加到List<Person>或其他集合的逻辑,内存必然会飙升到接近文件大小。确保处理单个对象后不保留其引用,仅在必要时临时使用。

4. 手动使用Utf8JsonReader实现极致内存控制

如果DeserializeAsyncEnumerable的封装仍有内存开销,可直接使用Utf8JsonReader逐对象解析,完全控制内存分配:

await using var stream = File.OpenRead("large.json");
using var buffer = MemoryPool<byte>.Shared.Rent(1024 * 1024); // 1MB池化缓冲区
int bytesRead;
var reader = new Utf8JsonReader(new ReadOnlySequence<byte>(), isFinalBlock: false, state: default);

while ((bytesRead = await stream.ReadAsync(buffer.Memory)) > 0)
{
    var sequence = new ReadOnlySequence<byte>(buffer.Memory.Slice(0, bytesRead));
    reader = new Utf8JsonReader(sequence, isFinalBlock: bytesRead == 0, reader.CurrentState);
    
    // 跳过数组开始标记
    if (reader.TokenType == JsonTokenType.StartArray)
        reader.Read();
    
    while (reader.TokenType != JsonTokenType.EndArray)
    {
        if (reader.TokenType == JsonTokenType.StartObject)
        {
            var person = JsonSerializer.Deserialize<Person>(ref reader);
            ProcessPerson(person);
        }
        else
        {
            reader.Read();
        }
    }
}

这种方式直接复用池化内存,避免中间缓冲区的冗余分配。

5. 基准测试的正确姿势

确保基准测试使用Release模式运行,且禁用BenchmarkDotNet的内存诊断以外的不必要功能。如果使用Debug模式,JIT优化未启用,内存开销会显著增加。

6. 检查数据集生成逻辑

确认你的数据集生成代码没有产生异常大的JSON对象,或意外生成了嵌套过深的结构。单个对象过大也会导致内存占用超出预期。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 10:44:52