【问题标题】:Am I creating a leak here?我在这里制造泄漏吗?
【发布时间】:2019-08-15 06:59:20
【问题描述】:

我正在使用 NETCore 3.0 的 System.Text.Json 命名空间中的新 JsonSerializer 来反序列化 Json 文档,如下所示:

var result = JsonSerializer.Deserialize<Response>(json, options);

响应定义为:

public class Response
{
    public string Foo { get; set; }
    public JsonElement Bar { get; set; }
}

JsonDocument 实现 IDisposable 的事实让我想知道,如果保留对可包含在 JsonDocument 中的元素 (Bar) 的引用,是否会造成内存泄漏?

请注意,一般情况下,我会避免将数据存储为像这样的“变体”类型。不幸的是,Bar 属性值的结构在编译时是未知的。

我的怀疑源于 System.Text.Json 宣传的懒惰评估的优势,我不确定这是否涉及延迟 I/O。

【问题讨论】:

  • 我认为 IDisposable 是指进行反序列化的对象。不是结果。
  • @JohnB 好吧,请记住,System.Text.Json 的广告优势之一是惰性评估。我不确定这是否涉及延迟 I/O。
  • 你这里没有使用JsonDocument,只有JsonElement,它只是一个结构体,没有实现IDisposable。我认为这两件事是独立的
  • @ADyson 我知道,但是如果反序列化器实际上使用 JsonDocument 而不是直接使用 Utf8JsonReader,该元素可以很好地保持对用于反序列化的实际文档的反向引用。跨度>
  • 没有泄漏。 JsonSerializer 有一个内置转换器JsonConverter&lt;JsonElement&gt;,它调用JsonElement.Clone()。此方法将JsonElement 从其主机JsonDocument 中复制出来,作为一个独立的对象,允许处理文档。

标签: c# json idisposable .net-core-3.0 system.text.json


【解决方案1】:

从对来源 (https://github.com/dotnet/corefx/blob/master/src/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs) 的简要调查来看,JsonDocument Dispose 似乎将“租用”字节返回到共享数组池并进行了一些常规清理。 JsonDocument 的某些实例被标记为不可丢弃,在这种情况下 Dispose 不会做任何事情。 您可以使用反射检查您的实例的此标志 - 如果您的实例没有将内部 IsDisposable 标志设置为 true,则无需担心,因为 Dispose 无论如何都不会做任何事情。

我认为在正常情况下,JsonDocument 解析器应该自行清理,并且解析器完成后应该没有租用的字节或内部数据。

不依赖特定的实现总是安全的,因为它可能会改变并且只存储对所需元素的引用。您可能应该将 JSON 元素重新映射到您的模型,我认为这就是 JSON 反序列化的全部目的

快速测试:

        var parentField = result.Bar.GetType().GetMember("_parent", MemberTypes.Field, BindingFlags.Instance | BindingFlags.NonPublic)[0] as FieldInfo;
        var parentDocument = parentField.GetValue(result.Bar);

        var isDisposableProperty = parentDocument.GetType().GetProperty("IsDisposable", BindingFlags.Instance | BindingFlags.NonPublic) as PropertyInfo;
        Console.WriteLine(isDisposableProperty.GetValue(parentDocument)); // false

证明JsonElement持有的JsonDocument实例不是一次性的。

【讨论】:

  • 在这样的通用结构中保留数据,通常我会不惜一切代价避免。 Bar 属性的问题是编译时值的结构是未知的。
  • 出色的调查。 +1
  • “我认为在正常情况下,JsonDocument 解析器应该自行清理,并且不应该留下任何租用的字节”序列化程序确实如此(如果你能看到它,它没有使用 ArrayPool),但是不是当你直接调用 JsonDocument.Parse 时(在不同的答案中有更多解释)。
【解决方案2】:

当您调用 JsonDocument.Parse 时,它使用池化数组来避免高吞吐量下的垃圾收集暂停。

当您处理该文档时,它会将数组放回池中,如果您丢失了引用并且它被垃圾收集......它就好像它根本没有被丢弃一样(稍微 em> 更糟糕的是,因为运行时的某些其他方面可能会突然出现暂停和 GC 启动的地方,因为池中的数组现在更少了)。

JsonElement 有一个 Clone 方法(但不是 ICloneable),它使用不使用池化数组的 JsonDocument 实例返回(相关)数据的副本。

JsonSerializer,在返回JsonElement 值时,总是调用Clone(),然后处理掉原来的JsonDocument。因此,当您使用JsonSerializer 并获得JsonElement(直接或通过溢出属性)时,就像JsonDocument 是在没有ArrayPool 优化的情况下构建的一样......所以一切都很好。

【讨论】:

  • 您能否在答案中详细说明“如果您丢失了参考并且它被垃圾收集......它就像它根本不是一次性的一样”?或者也许尝试用不同的方式来解释它,因为这对我来说有点不清楚。
  • @AndreiBozantan 我会尝试:“假设类型未声明为 IDisposable,会发生什么?当对象不再是根时,它会被垃圾收集。这与这里发生的情况相同——不同之处在于,如果处理得当,一些内存会返回到内存池而不是垃圾收集器,这会带来一些性能优势。无论哪种方式,都不会发生泄漏。”
猜你喜欢
  • 1970-01-01
  • 2018-09-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多