Я создаю утечку здесь?

Я использую новый JsonSerializer из пространства имен System.Text.Json в NETCore 3.0 для десериализации документов 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 рекламирует силу ленивой оценки, и я не уверен, что это связано с отложенным вводом / выводом.

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

Доказывает, что экземпляр JsonDocument, хранящийся в JsonElement, не является одноразовым.

Когда ты звонишь JsonDocument.Parse он использует объединенные массивы, чтобы избежать пауз при сборке мусора при высокой пропускной способности.

Когда вы удаляете этот документ, он помещает массивы обратно в пул, если вы теряете ссылку и он получает сборщик мусора... это то же самое, как если бы он вообще не был одноразовым (немного хуже, поскольку некоторые другие аспекты время выполнения может внезапно оказаться там, где все приостанавливается, и GC срабатывает, потому что в пуле теперь меньше массивов).

JsonElement имеет Clone метод (но не ICloneable), который возвращает копию (соответствующих) данных с использованием JsonDocument экземпляр, который не использует объединенные массивы.

JsonSerializer при возврате JsonElement ценности, всегда звонки Clone() а затем утилизирует оригинал JsonDocument. Итак, когда вы используетеJsonSerializer и получить JsonElement (напрямую или через свойства переполнения) это то же самое, как если бы JsonDocument был построен без ArrayPool оптимизация... так что все нормально.

Другие вопросы по тегам