10

我正在使用 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的事实让我想知道,如果保留对Bar可以包含在 JsonDocument 中的元素 () 的引用,是否会造成内存泄漏?

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

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

4

2 回答 2

9

从对来源的简要调查(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 的实例不是一次性的。

于 2019-08-15T07:18:47.037 回答
7

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

当您处理该文档时,它会将数组放回池中,如果您丢失了引用并且它会被垃圾收集......这就像它根本不是一次性的一样(稍微更糟,因为运行时可能会突然暂停,GC 开始运行,因为池中的数组现在更少了)。

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

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

于 2019-09-26T14:56:16.463 回答