我在这里制造泄漏吗?
Am I creating a leak here?
我正在使用来自 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
) 的引用是否会造成内存泄漏?
请注意,通常我会避免将数据存储为这样的 "variant" 类型。不幸的是 Bar
属性 值的结构在编译时是未知的。
我的怀疑源于 System.Text.Json 懒惰评估的广告优势,我不确定这是否涉及延迟 I/O。
从对来源的简要调查 (https://github.com/dotnet/corefx/blob/master/src/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs) 看来,JsonDocument 将 returns "rented" 字节分配到共享数组池并进行了一些常规清理。
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实例不是一次性的
当您调用 JsonDocument.Parse
时,它使用池化数组来避免高吞吐量下的垃圾收集暂停。
当您处理该文档时,它会将数组放回池中,如果您丢失了引用并且它会被垃圾收集...就好像它根本没有被处理过一样(稍微 更糟,因为运行时的某些其他方面可能突然暂停并且 GC 启动,因为池现在的数组更少了)。
JsonElement
有一个 Clone
方法(但不是 ICloneable
),它 returns 使用 JsonDocument
的(相关)数据的副本不使用池数组的实例。
JsonSerializer,当返回JsonElement
值时,总是调用Clone()
,然后释放原来的JsonDocument
。因此,当您使用 JsonSerializer
并获得 JsonElement
(直接或通过溢出属性)时,就好像 JsonDocument
是在没有 ArrayPool
优化的情况下构建的一样......所以一切都是很好。
我正在使用来自 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
) 的引用是否会造成内存泄漏?
请注意,通常我会避免将数据存储为这样的 "variant" 类型。不幸的是 Bar
属性 值的结构在编译时是未知的。
我的怀疑源于 System.Text.Json 懒惰评估的广告优势,我不确定这是否涉及延迟 I/O。
从对来源的简要调查 (https://github.com/dotnet/corefx/blob/master/src/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs) 看来,JsonDocument 将 returns "rented" 字节分配到共享数组池并进行了一些常规清理。 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实例不是一次性的
当您调用 JsonDocument.Parse
时,它使用池化数组来避免高吞吐量下的垃圾收集暂停。
当您处理该文档时,它会将数组放回池中,如果您丢失了引用并且它会被垃圾收集...就好像它根本没有被处理过一样(稍微 更糟,因为运行时的某些其他方面可能突然暂停并且 GC 启动,因为池现在的数组更少了)。
JsonElement
有一个 Clone
方法(但不是 ICloneable
),它 returns 使用 JsonDocument
的(相关)数据的副本不使用池数组的实例。
JsonSerializer,当返回JsonElement
值时,总是调用Clone()
,然后释放原来的JsonDocument
。因此,当您使用 JsonSerializer
并获得 JsonElement
(直接或通过溢出属性)时,就好像 JsonDocument
是在没有 ArrayPool
优化的情况下构建的一样......所以一切都是很好。