Я нахожусь в процессе переноса нашей кодовой базы Java с Java 7 (80) на Java 8 (162). (Да... мы на переднем крае технологий.)
После переключения у меня возникли проблемы с загрузкой файлов ресурсов XML из развернутых jar-файлов в среде с большим количеством параллельных операций. Доступ к файлам ресурсов осуществляется с помощью try-with-resources
и анализируется через SAX:
try {
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
try (InputStream in = MyClass.class.getResourceAsStream("resource.xml")) {
parser.parse(in, new DefaultHandler() {...});
}
} catch (Exception ex) {
throw new RuntimeException("Error loading resource.xml", ex);
}
Пожалуйста, поправьте меня, если я ошибаюсь, но это похоже на подход, который обычно рекомендуется для чтения файлов ресурсов.
Это отлично работает в среде IDE, но после развертывания в банке я часто (но не всегда и не всегда с одним и тем же файлом ресурсов) получаю IOException
со следующей трассировкой стека:
Caused by: java.io.IOException: Stream closed
at java.util.zip.InflaterInputStream.ensureOpen(InflaterInputStream.java:67)
at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:142)
at java.io.FilterInputStream.read(FilterInputStream.java:133)
at com.sun.org.apache.xerces.internal.impl.XMLEntityManager$RewindableInputStream.read(XMLEntityManager.java:2919)
at com.sun.org.apache.xerces.internal.impl.io.UTF8Reader.read(UTF8Reader.java:302)
at com.sun.org.apache.xerces.internal.impl.XMLEntityScanner.load(XMLEntityScanner.java:1895)
at com.sun.org.apache.xerces.internal.impl.XMLEntityScanner.scanName(XMLEntityScanner.java:728)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanStartElement(XMLDocumentFragmentScannerImpl.java:1279)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2784)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:602)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:505)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:842)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:771)
at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141)
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1213)
at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:643)
at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl.parse(SAXParserImpl.java:327)
at javax.xml.parsers.SAXParser.parse(SAXParser.java:195)
Вопросы:
Что тут происходит?
Я делаю что-то не так, как я читаю/разбираю эти файлы ресурсов? (Или вы можете предложить улучшения?)
Что я могу сделать, чтобы решить эту проблему?
Первоначальные мысли:
Первоначально, поскольку я видел проблему только тогда, когда код был развернут в банке, я подумал, что это как-то связано с доступом через JarFile
- возможно, к файлам ресурсов обращается общий JarFile
, и что когда один из этих входов ресурсов потоки закрыты, то есть закрытие JarFile
, и это закрытие всех других открытых входных потоков. Например, есть вопрос SO, показывающий похожее поведение (когда OP был напрямую обработка JarFile
s). Кроме того, был похожий отчет об ошибке, но он вернулся в Java. 6 и, по-видимому, исправлено в Java 7.
Обновление 1:
После дальнейшей отладки эта проблема возникает из-за того, что синтаксический анализатор XML закрывает InputStream
после завершения его синтаксического анализа. (Мне это кажется немного странным - действительно, это вызвало эти вопросы в отношении DOM и SAX синтаксический анализ - но мы идем.) Таким образом, мое текущее лучшее предположение состоит в том, что SAXParser
(или на самом деле внизу в XMLEntityManager
) вызывает InputStream.close()
, но есть какое-то состояние гонки в отношении состояния?
Похоже, это не связано с использованием try-with-resources, т. е. учитывая, что SAXParser закрывает InputStream, я попытался удалить try-with-resources, но все равно получаю те же ошибки/трассировку стека.
Обновление 2:
После еще большей отладки я обнаружил, что XMLEntityManager$RewindableInputStream
закрывается, до того, как он закончил чтение XML-файла. Интересно, что я вижу это только в сильно параллельной среде, но я все еще вижу это, даже если я устанавливаю блокировки вокруг всех наших возможных загрузок XML-ресурсов, то есть когда только один XML-ресурс читается за раз.
Трассировка стека места закрытия XMLEntityManager$RewindableInputStream — до завершения чтения файла — выглядит следующим образом:
at java.util.zip.InflaterInputStream.close(InflaterInputStream.java:224)
at java.util.zip.ZipFile$ZipFileInflaterInputStream.close(ZipFile.java:417)
at java.io.FilterInputStream.close(FilterInputStream.java:181)
at sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream.close(JarURLConnection.java:108)
at com.sun.org.apache.xerces.internal.impl.XMLEntityManager$RewindableInputStream.close(XMLEntityManager.java:3005)
at com.sun.org.apache.xerces.internal.impl.io.UTF8Reader.close(UTF8Reader.java:674)
at com.sun.xml.internal.stream.Entity$ScannedEntity.close(Entity.java:422)
at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.endEntity(XMLEntityManager.java:1387)
at com.sun.org.apache.xerces.internal.impl.XMLEntityScanner.load(XMLEntityScanner.java:1916)
at com.sun.org.apache.xerces.internal.impl.XMLEntityScanner.skipSpaces(XMLEntityScanner.java:1629)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$TrailingMiscDriver.next(XMLDocumentScannerImpl.java:1371)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:602)
at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:112)
at com.sun.org.apache.xerces.internal.impl.XMLStreamReaderImpl.next(XMLStreamReaderImpl.java:553)
at com.sun.xml.internal.stream.XMLEventReaderImpl.nextEvent(XMLEventReaderImpl.java:83)
Итак, на данный момент мое лучшее предположение (и это только то), что есть какая-то нишевая ошибка параллелизма в основном файловом менеджере Java XML/входном потоке и т. д. Может быть, результат синхронизации, возможно? (Если это так, я не уверен, была ли это ранее существовавшая ошибка, которая была обнаружена только благодаря улучшениям параллелизма в Java 8, или новая ошибка в Java 8.)
(Тем не менее, я не подавал отчет об ошибке, так как не думаю, что у меня есть достаточно, чтобы сказать, что есть ошибка, или достаточно информации, чтобы сообщить любому, кто будет ее искать.)
Решение:
Учитывая, что проблема заключалась в использовании основных библиотек Java XML, я решил написать свою собственную (в основном на основе StAX). К счастью, наши XML-файлы ресурсов довольно просты и понятны, поэтому мне нужно было реализовать только часть функций основных синтаксических анализаторов Java XML.
Обновление 3:
Вышеупомянутый обходной путь действительно улучшил ситуацию - например, он решил конкретные случаи проблемы, с которой я столкнулся. Однако после этого я обнаружил, что все еще получаю случаи, когда InputStream из ресурса в JAR закрывался во время его чтения. Теперь трассировка стека выглядит так:
java.lang.IllegalStateException: zip file closed
at java.util.zip.ZipFile.ensureOpen(ZipFile.java:686)
at java.util.zip.ZipFile.access$200(ZipFile.java:60)
at java.util.zip.ZipFile$ZipEntryIterator.hasNext(ZipFile.java:508)
at java.util.zip.ZipFile$ZipEntryIterator.hasMoreElements(ZipFile.java:503)
at java.util.jar.JarFile$JarEntryIterator.hasNext(JarFile.java:253)
at java.util.jar.JarFile$JarEntryIterator.hasMoreElements(JarFile.java:262)
Поиск проблем, связанных с этой трассировкой стека, привел меня к этому вопросу, и предлагают, чтобы я контролировал URLConnection
, чтобы не кэшировать соединения, чтобы они не были разделены: [URLConnection.setUseCaches(boolean)][6]
Таким образом, я попробовал это (см. Ответ ниже для реализации), и, похоже, он работает и стабильно. Я даже вернулся и попробовал это с моими предыдущими основными анализаторами Java StAX, и все это, казалось, работало и стабильно. (Кроме того, в настоящее время я не решил, стоит ли сохранять мои собственные XML-парсеры — они кажутся немного более производительными благодаря подсветке, но это компромисс с дополнительными требованиями к обслуживанию.) Так что, вероятно, это не так. ошибка параллелизма в основных парсерах Java XML, но проблема с динамическими загрузчиками классов в JVM.
Обновление 4:
Я все больше склоняюсь к мнению, что это ошибка параллелизма в основной Java в отношении того, как она обрабатывает доступ к файлам ресурсов в виде потока из jar-файлов. Например, в org.reflections.reflections есть эта проблема, с которой я также столкнулся .
Я также видел эту проблему в отношении JBLAS, так что я получаю следующее исключение (и Возникла проблема):
Caused by: java.lang.NullPointerException: Inflater has been closed
at java.util.zip.Inflater.ensureOpen(Inflater.java:389)
at java.util.zip.Inflater.inflate(Inflater.java:257)
at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:152)
at java.io.FilterInputStream.read(FilterInputStream.java:133)
at java.io.FilterInputStream.read(FilterInputStream.java:107)
at org.jblas.util.LibraryLoader.loadLibraryFromStream(LibraryLoader.java:261)
at org.jblas.util.LibraryLoader.loadLibrary(LibraryLoader.java:186)
at org.jblas.NativeBlasLibraryLoader.loadLibraryAndCheckErrors(NativeBlasLibraryLoader.java:32)
at org.jblas.NativeBlas.<clinit>(NativeBlas.java:77)
JarFile
. Имейте в виду, что это также нарушит загрузку всего класса, поскольку поиск и чтение файлов классов работает точно так же. - person Holger   schedule 06.04.2018