Уменьшение объема памяти при использовании больших XML DOM'ов в Java

Наше приложение должно принимать данные клиента, представленные в формате XML (несколько файлов), и разбирать их в наш общий формат XML (один файл со схемой). Для этого мы используем фреймворк привязки данных XMLBeans от apache. Этапы этого процесса кратко описаны ниже.

Сначала мы берем необработанные объекты java.io.File, указывающие на XML-файлы клиента на диске, и загружаем их в коллекцию. Затем мы выполняем итерации по этой коллекции, создавая один apache.xmlbeans.XmlObject для каждого файла. После того, как все файлы были разобраны на XmlObjects, мы создаем 4 коллекции, содержащие отдельные объекты из XML документов, которые нас интересуют (для ясности, это не созданные вручную объекты, а то, что я могу описать как "прокси" объекты, созданные фреймворком apache XMLBeans). В качестве заключительного шага мы затем итерируем эти коллекции для создания нашего XML-документа (в памяти) и затем сохраняем его на диск.

Для большинства случаев использования этот процесс отлично работает и может быть легко запущен в JVM при указании аргумента командной строки '-Xmx1500m'. Однако проблемы возникают, когда клиент предоставляет нам "большие наборы данных". В данном случае большим является 123 Мб клиентского XML, распределенного по 7 файлам. Такие наборы данных приводят к тому, что наши коллекции в коде заполняются почти 40 000 вышеупомянутых "прокси-объектов". В таких случаях использование памяти просто зашкаливает. Я не получаю никаких исключений из памяти, программа просто зависает, пока не произойдет сборка мусора, освобождая небольшое количество памяти, затем программа продолжает работу, использует это новое пространство, и цикл повторяется. В настоящее время сеансы парсинга занимают 4-5 часов. Мы стремимся сократить это время до часа".

Важно отметить, что вычисления, необходимые для преобразования клиентского xml в наш xml, требуют перекрестных ссылок на все данные xml. Поэтому мы не можем реализовать последовательную модель парсинга или разбить этот процесс на небольшие блоки.

Что я пробовал до сих пор

Вместо того чтобы держать все 123 Мб клиентского xml в памяти, при каждом запросе данных загружайте файлы, находите данные и освобождайте ссылки на эти объекты. Это, кажется, уменьшает объем памяти, потребляемой во время процесса, но, как вы можете себе представить, количество времени, которое занимает постоянный ввод-вывод, сводит на нет преимущества уменьшения объема памяти.

Я подозревал, что проблема заключается в том, что мы храним XmlObject[] для файлов XML размером 123 Мб, а также коллекции объектов, взятых из этих документов (с помощью запросов xpath). Чтобы исправить ситуацию, я изменил логику так, чтобы вместо запросов к этим коллекциям, документы запрашивались напрямую. Идея заключается в том, что в этот момент не существует 4 массивных списков с 10-ю или 1000-ми объектами, а только большая коллекция XmlObjects. Это, как оказалось, совсем не меняет ситуацию, а в некоторых случаях еще больше увеличивает объем памяти.

Хватаясь за соломинку, я подумал, что XmlObject, который мы используем для создания нашего xml в памяти перед записью на диск, становится слишком большим, чтобы поддерживать его вместе со всеми данными клиента. Однако выполнение некоторых запросов sizeOf для этого объекта показало, что в самом большом размере этот объект занимает менее 10 Кб. После прочтения того, как XmlBeans управляет большими объектами DOM, кажется, что он использует некоторую форму буферизованного писателя и, как результат, управляет этим объектом достаточно хорошо.

Итак, теперь у меня нет идей; я не могу использовать SAX-подходы вместо DOM-подходов, требующих много памяти, поскольку нам нужно 100% клиентских данных в нашем приложении в любой момент времени; я не могу отложить запрос этих данных до тех пор, пока они нам абсолютно не понадобятся, поскольку процесс преобразования требует много циклов, а время дискового ввода-вывода не стоит сэкономленного места в памяти, и я не могу структурировать нашу логику таким образом, чтобы уменьшить количество места, занимаемого внутренними коллекциями java. Неужели мне не повезло? Должен ли я просто принять тот факт, что если я хочу разобрать 123 Мб данных xml в наш формат Xml, то я не смогу сделать это с выделенными 1500 м памяти? Хотя 123 Мб - это большой набор данных в нашей области, я не могу представить, что другим никогда не приходилось делать что-то подобное с гигабайтами данных за раз.

Другая информация, которая может быть важна

  • Я использовал JProbe, чтобы попытаться выяснить, может ли это сказать мне что-нибудь полезное. Хотя я профан в профилировании, я просмотрел их учебники по утечкам памяти и блокировкам потоков, понял их и, похоже, в нашем коде нет никаких утечек или узких мест. После запуска приложения с большим набором данных мы быстро увидели на экране анализа памяти форму типа "пилы" (см. прикрепленное изображение) с пространством PS Eden, занятым массивным зеленым блоком PS Old Gen. Это наводит меня на мысль, что проблема здесь заключается в огромном количестве места, занимаемого коллекциями объектов, а не в утечке неиспользуемой памяти.

JProbe trace of memory usage during parsing of large dataset

  • Я работаю на 64-битной платформе Windows 7, но это должно работать на 32-битной среде.
6
задан Braiam 31 March 2017 в 13:33
поделиться