Ресурс Java InputStream закрывается?
Я нахожусь в процессе миграции нашей кодовой базы 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, но после развертывания в jar я часто (но не всегда, и не всегда с одним и тем же файлом ресурсов) получаю 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
- perhapsthe файлы ресурсов доступны общим JarFile
и что, когда один из этих потоков ввода ресурсов закрыт, это закрывает JarFile
и это закрывает все другие открытые входные потоки. Например, есть вопрос SO, демонстрирующий похожее поведение (когда OP непосредственно обрабатывал JarFile
с). Кроме того, был похожий отчет об ошибке, но он был в 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)
Итак, на данный момент я предпочитаю (и это только так), что есть какая-то нишевая ошибка параллелизма в основном файловом менеджере XML 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)
1 ответ
Как я объясняю в "Обновлении 3", я нашел следующее жизнеспособное и стабильное решение:
try {
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
URLConnection connection = MyClass.class.getResource("resource.xml").openConnection()
connection.setUseCaches(false);
try (InputStream in = connection.getInputStream()) {
parser.parse(in, new DefaultHandler() {...});
}
} catch (Exception ex) {
throw new RuntimeException("Error loading resource.xml", ex);
}