Jar files opened via URL, URLConnection, or URLClassLoader utilize an on-by-default cache of open JarFile instances. This map is maintained by JarFileFactory and maps jar URLs to already-opened JarFiles, presumably to reduce the number of open files when the same jar is used by multiple consumers.
https://github.com/openjdk/jdk/blob/master/src/java.base/unix/classes/sun/net/www/protocol/jar/JarFileFactory.java#L80-L97
In the cases of URL and URLConnection, the "connection" to the jar resource is made via this cached JarFile, which is left in the cache after accessing the resource. The closing of an InputStream from URL.openStream or closing the connection acquired from URL.openConnection does not damage the cached JarFile.
URLClassLoader, on the other hand, stores in its list of "closeables" any jar files used to acquire resources.
https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/net/URLClassLoader.java#L294-L300
Unless the cache use is disabled via URLConnection.setUseCaches(false), closing the URLClassLoader will also close these JarFile instances.
https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/net/URLClassLoader.java#L349-L359
Closing the JarFile instance is supposed to also remove it from the global cache.
https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/sun/net/www/protocol/jar/URLJarFile.java#L167-L169
Unfortunately, the removal and close is not performed as an atomic operation, which means there's a window of time where another thread could acquire a reference to a doomed JarFile. Basically, if two isolated URLClassLoader happen to access the same jar file concurrently, and one of them is closed, the other may error out.
https://github.com/openjdk/jdk/blob/master/src/java.base/unix/classes/sun/net/www/protocol/jar/JarFileFactory.java#L112-L118
The result is that other threads may attempt to use JarFile instances that are about to be closed.
These errors have been reported many times in many forms but usually without a clear reproduction or analysis. The following example fails quickly:
https://gist.github.com/headius/32416a79faf14f63d660c40d83021bcf
Example errors:
{noformat}
java.lang.IllegalStateException: zip file closed
at java.base/java.util.zip.ZipFile.ensureOpen(ZipFile.java:915)
at java.base/java.util.zip.ZipFile.getInputStream(ZipFile.java:378)
at java.base/java.util.jar.JarFile.getInputStream(JarFile.java:835)
at java.base/sun.net.www.protocol.jar.JarURLConnection.getInputStream(JarURLConnection.java:167)
at java.base/java.net.URLClassLoader.getResourceAsStream(URLClassLoader.java:328)
at BrokenURLClassLoader.lambda$main$0(BrokenURLClassLoader.java:27)
java.lang.NullPointerException
at java.base/sun.net.www.protocol.jar.JarURLConnection.connect(JarURLConnection.java:133)
at java.base/sun.net.www.protocol.jar.JarURLConnection.getInputStream(JarURLConnection.java:155)
at java.base/java.net.URLClassLoader.getResourceAsStream(URLClassLoader.java:328)
at BrokenURLClassLoader.lambda$main$0(BrokenURLClassLoader.java:27)
{noformat}
We have also seen occasional "inflater closed" errors within resource/classloading logic while working on JRuby. They may be related but this case does not appear to reproduce them.
I know of two possible workarounds for user code:
* Disable the use of these caches by always acquiring the intermediate URLConnection before proceeding to access resources. It is not possible to set an entire URLClassLoader to not use the cache, however, so this does not help classloading requests from elsewhere.
* Do not close URLClassLoader. This will leave open JarFile instances in the cache, which may be benign (static jar files from the filesystem) or malignant (temporary jar files created or unpacked by library or app code).
To summarize: URLClassLoader's usage of the global JarFile cache leads to improperly closed JarFile instances and subsequent errors under concurrent load. All uses of URLClassLoader with jar files will potentially fail under concurrent load. This affects OpenJDK versions at least back to Java 7, when URLClassLoader.close was added.