Ресурс 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);
} 
Другие вопросы по тегам