Java 资源 InputStream 正在关闭?
Java Resource InputStream being closed?
我正在将我们的 Java 代码库从 Java 7 (80) 迁移到 Java 8 (162)。 (是的......我们处于技术的最前沿。)
切换后,在高度并发的环境中从已部署的 jar 加载 XML 资源文件时,我一直遇到问题。正在使用 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)
问题:
这是怎么回事?
我是不是做错了什么,我是如何reading/parsing这些资源文件的? (或者您能提出改进建议吗?)
如何解决这个问题?
初步想法:
最初,因为我只在将代码部署到 jar 中时才看到问题,所以我认为这与通过 JarFile
进行访问有关 - 也许资源文件正在被共享 JarFile
,并且当其中一个资源输入流关闭时,即关闭 JarFile
,并且关闭所有其他打开的输入流。例如,有一个 SO question showing similar behaviour (when the OP was directly handling the JarFile
s). Also, there was a similar looking bug report,但它在 Java 6 中返回并且显然已在 Java 7 中修复。
更新 1:
进一步调试后,这个问题似乎是因为 XML 解析器在完成解析后关闭了 InputStream
。 (这对我来说似乎有点奇怪 - 实际上它提示了这些与 DOM and SAX 解析相关的问题 - 但我们开始了。)因此,我目前最好的猜测是 SAXParser
(或者实际上在XMLEntityManager
) 正在调用 InputStream.close()
,但是状态存在某种竞争条件?
它似乎与 try-with-resources 的使用无关 - 即假设 SAXParser 正在关闭 InputStream,我已经尝试删除 try-with-resources,但我仍然得到相同的结果errors/stack 跟踪。
更新二:
经过多次调试,我发现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:
上述变通办法确实有所改善 - 例如,它解决了我面临的问题的特定实例。然而,在那之后,我发现我仍然遇到这样的情况,即来自 JAR 中资源的 InputStream 在被读取时被关闭。现在堆栈跟踪是这样的:
java.lang.IllegalStateException: zip file closed
at java.util.zip.ZipFile.ensureOpen(ZipFile.java:686)
at java.util.zip.ZipFile.access0(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)
搜索与该堆栈跟踪相关的问题使我找到了这个 question,并建议我控制 URLConnection
,以免缓存连接,这样它们就不会被共享: [URLConnection.setUseCaches(boolean)][6]
因此,我尝试了这个(请参阅下面的答案以了解实施情况)并且它似乎可以正常工作且稳定。我什至回去用我以前的核心 Java StAX 解析器进行了尝试,一切似乎都在工作并且稳定。 (除此之外,我目前还不确定是否保留我的自定义 XML 解析器 - 由于被点亮,它们似乎性能更高一些,但这是与额外维护要求的权衡。)所以,这可能不是核心 Java XML 解析器中的并发错误,而是 JVM 中动态类加载器的问题。
更新四:
我越来越认为这是核心 Java 中的一个并发错误,关于它如何处理对资源文件的访问,作为流,从 jars 中。比如org.reflections.reflections中就有这个问题,我也遇到过
我也看到了关于 JBLAS, such that I get the following exception (and the issue 提出的这个问题):
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)
正如我在 'Update 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);
}
我正在将我们的 Java 代码库从 Java 7 (80) 迁移到 Java 8 (162)。 (是的......我们处于技术的最前沿。)
切换后,在高度并发的环境中从已部署的 jar 加载 XML 资源文件时,我一直遇到问题。正在使用 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)
问题:
这是怎么回事?
我是不是做错了什么,我是如何reading/parsing这些资源文件的? (或者您能提出改进建议吗?)
如何解决这个问题?
初步想法:
最初,因为我只在将代码部署到 jar 中时才看到问题,所以我认为这与通过 JarFile
进行访问有关 - 也许资源文件正在被共享 JarFile
,并且当其中一个资源输入流关闭时,即关闭 JarFile
,并且关闭所有其他打开的输入流。例如,有一个 SO question showing similar behaviour (when the OP was directly handling the JarFile
s). Also, there was a similar looking bug report,但它在 Java 6 中返回并且显然已在 Java 7 中修复。
更新 1:
进一步调试后,这个问题似乎是因为 XML 解析器在完成解析后关闭了 InputStream
。 (这对我来说似乎有点奇怪 - 实际上它提示了这些与 DOM and SAX 解析相关的问题 - 但我们开始了。)因此,我目前最好的猜测是 SAXParser
(或者实际上在XMLEntityManager
) 正在调用 InputStream.close()
,但是状态存在某种竞争条件?
它似乎与 try-with-resources 的使用无关 - 即假设 SAXParser 正在关闭 InputStream,我已经尝试删除 try-with-resources,但我仍然得到相同的结果errors/stack 跟踪。
更新二:
经过多次调试,我发现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:
上述变通办法确实有所改善 - 例如,它解决了我面临的问题的特定实例。然而,在那之后,我发现我仍然遇到这样的情况,即来自 JAR 中资源的 InputStream 在被读取时被关闭。现在堆栈跟踪是这样的:
java.lang.IllegalStateException: zip file closed
at java.util.zip.ZipFile.ensureOpen(ZipFile.java:686)
at java.util.zip.ZipFile.access0(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)
搜索与该堆栈跟踪相关的问题使我找到了这个 question,并建议我控制 URLConnection
,以免缓存连接,这样它们就不会被共享: [URLConnection.setUseCaches(boolean)][6]
因此,我尝试了这个(请参阅下面的答案以了解实施情况)并且它似乎可以正常工作且稳定。我什至回去用我以前的核心 Java StAX 解析器进行了尝试,一切似乎都在工作并且稳定。 (除此之外,我目前还不确定是否保留我的自定义 XML 解析器 - 由于被点亮,它们似乎性能更高一些,但这是与额外维护要求的权衡。)所以,这可能不是核心 Java XML 解析器中的并发错误,而是 JVM 中动态类加载器的问题。
更新四:
我越来越认为这是核心 Java 中的一个并发错误,关于它如何处理对资源文件的访问,作为流,从 jars 中。比如org.reflections.reflections中就有这个问题,我也遇到过
我也看到了关于 JBLAS, such that I get the following exception (and the issue 提出的这个问题):
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)
正如我在 'Update 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);
}