Проблемы с OutOfMemoryError с pdfbox после использования Splitter для нескольких файлов PDF

У меня есть пакет PDF-файлов, который мне нужно разделить на несколько PDF-файлов для каждой страницы. Я написал сценарий, который обрабатывает PDF-файлы и работает с отдельными файлами или парой файлов. Но при обработке нескольких PDF-файлов (всего 6000 страниц) в конечном итоге заканчивается память и печатается «Предупреждение: вы не закрыли PDF-документ». Я заставил его работать, уменьшив настройки памяти с помощью MemoryUsageSetting.setupMixed(50_000_000), но если я установлю его на 200 МБ, он всегда закончится. Я убежден, что существует внутренний COSDocument, открытый pdfbox, который я не могу закрыть, и поэтому я вижу предупреждение. Я знаю, что закрываю все PDDocument, что создаю. На самом деле я написал код для проверки каждого экземпляра, к которому у меня есть доступ, и ни одно из этих операторов журнала не появилось. Вот мой код:

    int splitPdf( File pdfFile ) {
        String filePrefix = prefix
        if ( !filePrefix ) filePrefix = pdfFile.name[0..<(pdfFile.name.lastIndexOf("."))]
        logger.info("Splitting ${pdfFile.name} pages ${startAtPage} - ${endAtPage} every ${splitPagesEvery} pages")
        PDDocument document = PDDocument.load( pdfFile, MemoryUsageSetting.setupTempFileOnly() )
        try {
            Splitter splitter = new Splitter()
            splitter.setStartPage(startAtPage)
            splitter.setEndPage(endAtPage)
            splitter.setSplitAtPage(splitPagesEvery)
            splitter.memoryUsageSetting = MemoryUsageSetting.setupMixed(50_000_000)
            int page = startAtPage > 0 ? startAtPage : 1
            splitter.split(document).each { PDDocument doc ->
                String filename = "${filePrefix}-${page}.pdf"
                try {
                    if ( extractions ) {
                        PDFTextStripper stripper = new PDFTextStripper()
                        String pageText = stripper.getText( doc )
                        Map<String,String> results = extractions.collectEntries([filename: filename]) { name, spec ->
                            [ name, spec.call(pageText) ]
                        }
                        this.manifest.write(results)
                    }
                    doc.save( new File( destDir, filename) )
                    page++
                } finally {
                    doc.close()
                    if ( !doc.document.isClosed() ) logger.info("${filename} is NOT closed!")
                }
            }
            return page - startAtPage
        } finally {
            document.close()
            if ( !document.document.isClosed() ) logger.info("${pdfFile.name} is NOT closed!")
        }
    }

У меня нет доказательств утечки памяти в pdfbox 2.0.29, но я не могу иначе объяснить, почему я вижу множество предупреждений о том, что PDF-документ нельзя закрывать. Я работаю над созданием еще одного сценария, который я могу запустить в профилировщике, чтобы проверить, превышает ли количество COSDOcuments то, что, как я знаю, должно быть там.

Мой вопрос: можно ли каким-либо образом создать дополнительный COSDocument внутри pdfbox, который не освобождается при разделении страниц?

в соответствии с предупреждением исходного кода просто сообщает вам, что close() не вызывается, а затем все равно вызывает close() github.com/BrentDouglas/pdfbox/blob/master/pdfbox/src/main/j‌​ava/…

daggett 05.10.2023 22:23

@daggett да, но я явно призываю закрыть каждый PDDocument в пределах области видимости, даже если он предупреждает и очищает себя. Он никогда не должен печатать это, если только не было вызвано close, и я не могу найти экземпляр, который бы не вызывал close. Так где же все эти COSDocuments, которые не закрываются?

chubbsondubs 06.10.2023 04:28

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

Tilman Hausherr 08.10.2023 05:51

@TilmanHausherr Спасибо за отзыв. Разве не было бы здорово, если бы у Splitter был близкий метод, позволяющий сделать именно это? Таким образом, после завершения легко все правильно очистить. Нет реальной необходимости разбираться в тонкостях отношений PDDocument между файлом верхнего уровня и PDDocument страниц, что приводит к лучшей инкапсуляции и меньшему количеству специальных знаний, необходимых для ее работы.

chubbsondubs 10.10.2023 02:41
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
4
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Хорошо, думаю, я нашел ответ. Предупреждение появляется только после того, как выдается OutOfMemoryError. Поскольку Splitter предварительно выделяет PDDocument для каждой страницы и сохраняет их в списке, вам необходимо иметь достаточно памяти для хранения всего файла вместе со всеми страницами в памяти. У меня был документ со страницами 2009 года, и это вызывало OOME в середине, и в этот момент все существующие страницы были помещены в очередь финализации. И это были экземпляры, выделенные Splitter, где я еще не посетил те, которые печатали бы это сообщение об ошибке. Исправление, которое я уже описал, заключалось в уменьшении кэш-памяти в MemorySettings, чтобы можно было обрабатывать все страницы. Как отметил Даггет, все эти страницы в конечном итоге будут бесплатными, но предупреждающее сообщение создает впечатление, что OOME возник из-за НЕ вызова close, что не соответствует действительности. Вам просто не хватает памяти. ¯_(ツ)_/¯

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