【问题标题】:.NET 7 JsonDocument Memory Leak?.NET 7 JsonDocument 内存泄漏?
【发布时间】:2022-11-21 16:50:55
【问题描述】:

内存占用一开始是4660k,后来增加到6920k,但最终并没有减少。

演示

static void Main(string[] args)
{
    string data = File.ReadAllText("./generated.json");
    Console.WriteLine("Begin parsing data...");
    for (var i = 0; i < 100; i++)
    {
        using (JsonDocument jsonDocument = JsonDocument.Parse(data))
        {
        }
        Thread.Sleep(650);
    }
    Console.WriteLine("Batch task ends...");
    GC.Collect();
    Console.ReadLine();
}

这是我的generated.json

【问题讨论】:

  • 怎么测的?没有实际测量各种内存消耗类型或 GC 世代的代码。如果您想要可用的数字,请将 BenchmarkDotNet 与 memory diagnosers 一起使用,以查看实际分配的内容
  • 您是在 Debug 还是 Release 中运行代码?
  • 此外,处置一个对象并不意味着删除它或它的缓冲区。特别是如果这些缓冲区是共享和重用的。 System.Text.Json 广泛使用池缓冲区,所以我猜 Dispose() 将内部缓冲区释放回池中,以便可以重复使用。您发布的代码没有分配 100 个缓冲区,而是只分配 1 个缓冲区并重新使用它。该缓冲区最后没有被孤立,所以它不是 GCd
  • @MatteoUmili 在调试中。
  • .NET 是开源的,Dispose 的代码表明,Dispose() 确实释放了租用的缓冲区。没有泄漏,恰恰相反

标签: c# json .net


【解决方案1】:

如果有泄漏,每个 .NET Core 开发人员都会注意到,因为 System.Text.Json 是 ASP.NET Core 的核心。事实上,问题的代码减少内存消耗99倍。

System.Text.Json 命名空间中的类旨在尽可能减少分配,不仅可以减少内存消耗,还可以提高速度。分配和垃圾收集缓冲区是昂贵的,尤其是对于大型缓冲区。重用缓冲区比删除它只是为了为下一次调用创建一个新的类似缓冲区要好。

他们这样做的方法之一是使用池缓冲区而不是每次都分配一个新缓冲区。 JsonDocument 的Dispose 释放了它使用的共享缓冲区,因此它们可以被重用:

    public void Dispose()
    {
        int length = _utf8Json.Length;
        if (length == 0 || !IsDisposable)
        {
            return;
        }

        _parsedData.Dispose();
        _utf8Json = ReadOnlyMemory<byte>.Empty;

        if (_extraRentedArrayPoolBytes != null)
        {
            byte[]? extraRentedBytes = Interlocked.Exchange<byte[]?>(ref _extraRentedArrayPoolBytes, null);

            if (extraRentedBytes != null)
            {
                // When "extra rented bytes exist" it contains the document,
                // and thus needs to be cleared before being returned.
                extraRentedBytes.AsSpan(0, length).Clear();
                ArrayPool<byte>.Shared.Return(extraRentedBytes);
            }
        }
        else if (_extraPooledByteBufferWriter != null)
        {
            PooledByteBufferWriter? extraBufferWriter = Interlocked.Exchange<PooledByteBufferWriter?>(ref _extraPooledByteBufferWriter, null);
            extraBufferWriter?.Dispose();
        }
    }

所有调用都涉及将合并的缓冲区和对象返回到池中。甚至_parsedData.Dispose()最终calls ArrayPool.Shared.Return(data)

        public void Dispose()
        {
            byte[]? data = Interlocked.Exchange(ref _data, null!);
            if (data == null)
            {
                return;
            }

            Debug.Assert(!_isLocked, "Dispose called on a locked database");

            // The data in this rented buffer only conveys the positions and
            // lengths of tokens in a document, but no content; so it does not
            // need to be cleared.
            ArrayPool<byte>.Shared.Return(data);
            Length = 0;
        }

【讨论】:

  • 我有一项服务,一天会收到数千条 json 消息,我只是担心它会占用机器的所有内存。
猜你喜欢
  • 2011-03-22
  • 1970-01-01
  • 2012-02-19
  • 1970-01-01
  • 2022-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多