У меня есть пакет 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, который не освобождается при разделении страниц?
@daggett да, но я явно призываю закрыть каждый PDDocument в пределах области видимости, даже если он предупреждает и очищает себя. Он никогда не должен печатать это, если только не было вызвано close, и я не могу найти экземпляр, который бы не вызывал close. Так где же все эти COSDocuments, которые не закрываются?
Несвязано, но важно: закрывать результирующие документы следует только после того, как все документы будут сохранены, поскольку иногда (в зависимости от исходного PDF-файла) происходит совместное использование ресурсов (например, шрифтов).
@TilmanHausherr Спасибо за отзыв. Разве не было бы здорово, если бы у Splitter был близкий метод, позволяющий сделать именно это? Таким образом, после завершения легко все правильно очистить. Нет реальной необходимости разбираться в тонкостях отношений PDDocument между файлом верхнего уровня и PDDocument страниц, что приводит к лучшей инкапсуляции и меньшему количеству специальных знаний, необходимых для ее работы.
Хорошо, думаю, я нашел ответ. Предупреждение появляется только после того, как выдается OutOfMemoryError. Поскольку Splitter предварительно выделяет PDDocument для каждой страницы и сохраняет их в списке, вам необходимо иметь достаточно памяти для хранения всего файла вместе со всеми страницами в памяти. У меня был документ со страницами 2009 года, и это вызывало OOME в середине, и в этот момент все существующие страницы были помещены в очередь финализации. И это были экземпляры, выделенные Splitter, где я еще не посетил те, которые печатали бы это сообщение об ошибке. Исправление, которое я уже описал, заключалось в уменьшении кэш-памяти в MemorySettings
, чтобы можно было обрабатывать все страницы. Как отметил Даггет, все эти страницы в конечном итоге будут бесплатными, но предупреждающее сообщение создает впечатление, что OOME возник из-за НЕ вызова close, что не соответствует действительности. Вам просто не хватает памяти. ¯_(ツ)_/¯
в соответствии с предупреждением исходного кода просто сообщает вам, что close() не вызывается, а затем все равно вызывает close() github.com/BrentDouglas/pdfbox/blob/master/pdfbox/src/main/java/…