Исключение из-за недостатка памяти при загрузке большого файла json с диска

c# json json.net

2198 просмотра

2 ответа

У меня есть файл json объемом 1,2 ГБ, который после десериализации должен дать мне список с 15 мил объектами.

Компьютер, на котором я пытаюсь десериализовать то же самое, - сервер Windows 2012 (64-разрядный) с 16 ядрами и 32 ГБ ОЗУ.

Приложение было построено с целью x64.

Несмотря на это, когда я пытаюсь прочитать документ json и преобразовать его в список объектов, я получаю исключение «Недостаточно памяти». когда я смотрю на диспетчер задач, я обнаруживаю, что используется только 5 ГБ памяти.

Коды, которые я попробовал, как показано ниже ..

а.

 string plays_json = File.ReadAllText("D:\\Hun\\enplays.json");

                plays = JsonConvert.DeserializeObject<List<playdata>>(plays_json);

б.

 string plays_json = "";
        using (var reader = new StreamReader("D:\\Hun\\enplays.json"))
        {
            plays_json = reader.ReadToEnd();
            plays = JsonConvert.DeserializeObject<List<playdata>>(plays_json);
        }

с.

 using (StreamReader sr = File.OpenText("D:\\Hun\\enplays.json"))
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(sr.ReadToEnd());
            plays_json = sb.ToString();
            plays = JsonConvert.DeserializeObject<List<playdata>>(plays_json);
        }

Вся помощь искренне ценится

Автор: Arnab Источник Размещён: 12.11.2019 09:28

Ответы (2)


8 плюса

Решение

Проблема в том, что вы читаете весь свой огромный файл в память, а затем пытаетесь сразу десериализовать его в огромный список. Вы должны использовать StreamReaderдля обработки вашего файла постепенно. Пример (b) в вашем вопросе не разрезает его, даже если вы используете там StreamReader, потому что вы все еще читаете весь файл через ReadToEnd(). Вы должны делать что-то вроде этого:

using (StreamReader sr = new StreamReader("D:\\Hun\\enplays.json"))
using (JsonTextReader reader = new JsonTextReader(sr))
{
    var serializer = new JsonSerializer();

    while (reader.Read())
    {
        if (reader.TokenType == JsonToken.StartObject)
        {
            // Deserialize each object from the stream individually and process it
            var playdata = serializer.Deserialize<playdata>(reader);

            ProcessPlayData(playdata);
        }
    }
}

ProcessPlayDataМетод должен обрабатывать один объект playdata , а затем в идеале записать результат в файл или базу данных , а не список в памяти ( в противном случае вы можете найти себя снова в той же ситуации , опять же ). Если вам необходимо сохранить результаты обработки каждого элемента в списке в памяти, вы можете рассмотреть возможность использования связанного списка или аналогичной структуры, которая не пытается распределить память в одном непрерывном блоке и не требует перераспределения и копирования когда нужно расширяться.

Автор: Brian Rogers Размещён: 10.10.2016 10:08

2 плюса

На мой взгляд, исключение из-за нехватки памяти может быть связано с одной из следующих причин.

Размер вашего объекта playsпревышает 2 ГБ, и по умолчанию максимальный размер объекта CLR в .NET составляет 2 ГБ (даже на x64). Смотрите здесь

Теперь ваш объект не должен быть 2 ГБ. Фрагментация в куче больших объектов (LOH) также может привести к тому, что объект размером менее 2 ГБ вызовет исключение нехватки памяти. (Любой объект размером более 80 КБ будет находиться в куче больших объектов)

Другой случай, когда ОС не может выделить непрерывный блок виртуальной памяти для вашего большого объекта, но я не думаю, что это будет так, поскольку вы упомянули, что у вас 32 ГБ ОЗУ.

Я бы не стал просто включать gcAllowVeryLargeObjects, если нет других вариантов. Я видел, как потребление памяти одной из моих больших систем обработки данных Apis возросло с 3 до 8 ГБ после включения этой настройки. (Хотя большинство из них были только зарезервированы) Я думаю, это потому, что вы позволяете приложению запрашивать у ОС столько памяти, сколько нужно для хранения большого объекта. Это может быть особенно проблематично, если вы размещаете другие приложения на том же сервере. Хорошо иметь верхний предел того, сколько памяти может занять управляемый объект.

Следует также отметить, что по умолчанию GC не сжимает LOH. Таким образом, это означает, что размер рабочего набора будет оставаться большим, пока не будет выполнена полная сборка мусора. (Вы можете вызывать GC для сжатия LOH начиная с .NET 4.5.1 и далее). Смотрите здесь

Я настоятельно рекомендую использовать профилировщик памяти, такой как dotMemory, чтобы сначала понять, что происходит внутри, прежде чем принимать какие-либо решения.

Если вы ориентируетесь на x64 и если это веб-приложение, убедитесь, что IIS также настроен на использование 64-битной версии. Смотрите здесь для локального IIS Express и IIS на сервере

На вашем месте я бы попытался разбить эту задачу на более мелкие партии.

Какова цель загрузки всего этого файла за один раз? Вы пытаетесь выполнить какую-либо операцию ввода-вывода с загруженными данными или какие-либо задачи, загружающие процессор?

Вот полезная ссылка на основы GC

Автор: Ren Размещён: 10.10.2016 10:35
32x32