我将尝试证明ClassLoader.getResourceAsStream()
打开两个InputStreams
,一个都不关闭,只返回一个给客户。我的逻辑正确吗?JDK 源码选自 jdk1.8.0_25
我在间隔(原始问题)中使用 Spring ClassPathResource 遇到了未关闭的资源问题,该问题ClassLoader.getResourceAsStream
用于获取InputStream
属性文件。
经过调查,我发现classLoader.getResourceAsStream
正在URL
通过URL url = getResource(name);
然后它正在打开该流,但URL url = getResource(name)
已经打开了该流。JDK源码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;
}
}
如果我们close()
以InputStream
这种方式提供,我们将仅关闭由url.openStream()
. JDK源码:
public final InputStream openStream() throws java.io.IOException {
return openConnection().getInputStream();
}
我假设,问题是,JDK 静默地打开一个流,URL url = getResource(name)
只是为了获取 URL 对象,该对象进一步用于创建**第二个(返回给客户端)流**。看看这个方法的来源:
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;
}
而现在,在我们转换为忘记打开的流getBootstrapResource(name)
的那一刻!:Resource
URL
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
}
为什么ucp.getResource(name);
要开放资源?让我们看看那个方法:this.getResource(var1, true);
,它委托给:
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;
}
为什么Resource var5 = var3.getResource(var1, var2);
要打开流?进一步看:
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();
}
};
}
我们可以看到openConnection()
and getInputStream()
,它们没有关闭,并且通过所有返回的调用回退,Resource
我们最终只使用getURL()
包装的方法Resource
而不关闭它InputStream
只是使用该URL
对象打开另一个 jetInputStream
并将其返回给客户端(客户端可以关闭coruse,但我们以第一个未关闭的流结束)。
那么,ClassLaoder.getResourceAsStream 是否因资源泄漏而中断?
实用方面:我getResourceAsStream
在try-with-resources
块中使用,并且在生产中仍然存在未关闭的资源问题,每 30 秒以这种方式加载文件名。此外,所有资源在垃圾回收时都关闭,这与方法中的文件流close()
一致finalize()
。