I will try to prove that ClassLoader.getResourceAsStream()
is opening two InputStreams
, closing none of it and returning only one to client. Is my logic correct? JDK sources are picked from jdk1.8.0_25
I've get into unclosed resources problem using Spring ClassPathResource in interval (original question), that is using ClassLoader.getResourceAsStream
to get InputStream
to a properties file.
After investigation, I found that classLoader.getResourceAsStream
is getting an URL
by URL url = getResource(name);
and then it is opening that stream, but URL url = getResource(name)
already opens that stream. JDK source of ClassLoader
:
public InputStream getResourceAsStream(String name) {
URL url = getResource(name); /* SILENTLY OPENS AND DON'T CLOSES STREAM */
try {
return url != null ? url.openStream() : null; /* SECOND OPEN !!! */
} catch (IOException e) {
return null;
}
}
If we will close()
the InputStream
provided that way, we will close only the stream opened by url.openStream()
. JDK source:
public final InputStream openStream() throws java.io.IOException {
return openConnection().getInputStream();
}
I'm supposing that, the problem is, the JDK opens a stream silently in URL url = getResource(name)
only to get URL object that is used further to create **second (returned to client) stream**. Look at this method sources:
public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name); <---- we end up calling that method
}
if (url == null) {
url = findResource(name);
}
return url;
}
And now, in getBootstrapResource(name)
the moment when we convert Resource
to URL
forgetting about opened stream in Resource
!:
private static URL getBootstrapResource(String name) {
URLClassPath ucp = getBootstrapClassPath();
Resource res = ucp.getResource(name); <---- OPENING STREAM [see further]
return res != null ? res.getURL() : null; <--- LOSING close() CAPABILITY
}
Why ucp.getResource(name);
is opening resource? Let's look into that method: this.getResource(var1, true);
, which delegates to:
public Resource getResource(String var1, boolean var2) {
if(DEBUG) {
System.err.println("URLClassPath.getResource(\"" + var1 + "\")");
}
URLClassPath.Loader var3;
for(int var4 = 0; (var3 = this.getLoader(var4)) != null; ++var4) {
Resource var5 = var3.getResource(var1, var2); <-------- OPENING STREAM
if(var5 != null) {
return var5;
}
}
return null;
}
Why Resource var5 = var3.getResource(var1, var2);
is opening stream? Look further:
Resource getResource(final String var1, boolean var2) {
final URL var3;
try {
var3 = new URL(this.base, ParseUtil.encodePath(var1, false));
} catch (MalformedURLException var7) {
throw new IllegalArgumentException("name");
}
final URLConnection var4;
try {
if(var2) {
URLClassPath.check(var3);
}
var4 = var3.openConnection(); <------------ OPENING STREAM
InputStream var5 = var4.getInputStream();
if(var4 instanceof JarURLConnection) {
JarURLConnection var6 = (JarURLConnection)var4;
this.jarfile = URLClassPath.JarLoader.checkJar(var6.getJarFile());
}
} catch (Exception var8) {
return null;
}
return new Resource() {
public String getName() {
return var1;
}
public URL getURL() {
return var3;
}
public URL getCodeSourceURL() {
return Loader.this.base;
}
public InputStream getInputStream() throws IOException {
return var4.getInputStream();
}
public int getContentLength() throws IOException {
return var4.getContentLength();
}
};
}
We can see openConnection()
and getInputStream()
, which are not closed, and falling back thrgough all the calls returning Resource
we are finally using only the getURL()
method wrapped in Resource
without closing it's InputStream
only to use that URL
object to open jet another InputStream
and return it to a client (which client can close of coruse, but we end with first stream unclosed).
So, is ClassLaoder.getResourceAsStream broken with leaking resources?
Practical side: I'm using getResourceAsStream
in try-with-resources
block, and still have unclosed resources problems in production with filename loaded such way every 30-seconds. More, all that resources are closed on garbage collection, which is consistent with file stream close()
in finalize()
method.