Java Resource InputStream正在关闭?

ama*_*ent 7 java jar resource-files ioexception java-8

我正在将Java代码库从Java 7(80)迁移到Java 8(162).(是的......我们处于技术前沿.)

在切换之后,我在一个高度并发的环境中从部署的jar加载XML资源文件时遇到了问题.正在使用try-with-resourcesSAX 访问和解析资源文件:

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);
} 
Run Code Online (Sandbox Code Playgroud)

如果我错了,请纠正我,但这似乎是通常建议阅读资源文件的方法.

这在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)
Run Code Online (Sandbox Code Playgroud)

问题:

  • 这里发生了什么?

  • 我做错了什么,我是如何阅读/解析这些资源文件的?(或者你可以建议改进吗?)

  • 我该怎么做才能解决这个问题?

初步想法:

最初,因为我只看到了问题,当代码在一个罐子里部署,我还以为是有人事做通过访问JarFile- .或许资源文件正在由共享访问JarFile,而当这些资源输入流中的一个关闭,即关闭JarFile,即关闭所有其他打开的输入流.例如,有一个SO问题显示类似的行为(当OP直接处理JarFiles时).此外,还有一个类似的错误报告,但这在Java 6中已经出现,并且在Java 7中显然已得到修复.

更新1:

进一步调试之后,这个问题似乎是因为XML解析器InputStream在解析它的时候正在关闭它.(这似乎有点奇怪,我-事实上它提示有关这些问题,DOMSAX解析-但我们去)这样,我目前最好的猜测是,SAXParser(在或实际下降XMLEntityManager)被调用InputStream.close(),但有是关于国家的某种竞争条件?

它似乎并不涉及到使用的尝试 - 以资源 - 除试穿与资源,即考虑到的SAXParser被关闭InputStream的,我已经试过了,我仍然得到同样的错误/堆栈跟踪.

更新2:

经过大量的调试后,我发现在读完XML文件之前它已经XMLEntityManager$RewindableInputStream关闭了.有趣的是,我只是在一个高度并发的环境中才看到这一点,但即使我把所有可能的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)
Run Code Online (Sandbox Code Playgroud)

所以,目前,我最好的猜测(并且只是那个)是核心Java XML文件管理器/输入流等中存在一些利基并发错误.也许是同步省略的结果?(如果是这种情况,我不确定这是否是一个预先存在的错误,只能通过Java 8中的并发性改进或Java 8中的新错误来揭示.)

(也就是说,我没有提交错误报告,因为我认为我没有足够的时间继续说有一个错误或足够的信息来通知任何想要寻找它的人.)

解决方法:

鉴于问题来自使用核心Java XML库,我决定编写自己的(主要基于StAX).幸运的是,我们的XML资源文件非常简单明了,因此我只需要在核心Java XML解析器中实现一小部分功能.

更新3:

上述解决方案确实改进了事情 - 因为它解决了我所面临的问题的特定情况.然而,在此之后,我发现我仍然遇到一个案例,其中来自JAR资源的InputStream在被读取时被关闭.现在堆栈跟踪是这样的:

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)
Run Code Online (Sandbox Code Playgroud)

搜索与该堆栈跟踪相关的问题让我想到了这个问题,并建议我控制它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)
Run Code Online (Sandbox Code Playgroud)

ama*_*ent 2

正如我在“更新 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);
} 
Run Code Online (Sandbox Code Playgroud)