JDK ClassLoader.getResourceAsStream坏了吗? (未公开的资源) [英] Is JDK ClassLoader.getResourceAsStream broken? (unclosed resources)
问题描述
我将尝试证明 ClassLoader.getResourceAsStream()
打开两个 InputStreams
,关闭无它只返回一个客户端。我的逻辑是否正确? JDK源是从jdk1.8.0_25中选取的
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
我在间隔中使用Spring ClassPathResource进入未封闭的资源问题(原始问题),即使用 ClassLoader.getResourceAsStream
获取 InputStream
到属性文件。
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.
经过调查,我发现 classLoader.getResourceAsStream
通过 URL url = getResource(name);
得到 URL
然后它打开流,但 网址url = getResource(名称)
已打开该流。 JDK源 ClassLoader
:
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;
}
}
如果我们关闭( )
InputStream
提供这种方式,我们将只关闭由 url.openStream()$ c打开的流$ C>。 JDK源:
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();
}
我想,问题是,JDK打开静默在 URL url = getResource(name)
中的流只能获取进一步用于创建** second (返回到客户端)流的URL对象** 。看看这个方法来源:
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;
}
现在,在 getBootstrapResource(名称)
我们将资源
转换为 URL
忘记<$ c中打开的流的时刻$ c>资源!:
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
}
为什么 ucp.getResource(name);
是否正在打开资源?让我们看一下这个方法: this.getResource(var1,true);
,委托给:
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;
}
为什么资源var5 = var3.getResource(var1, var2);
是开放流吗?进一步了解:
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();
}
};
}
我们可以看到 openConnection()
和 getInputStream()
,它们没有关闭,并且退回所有返回的调用资源
我们是最后只使用 Resource
中包含的 getURL()
方法,而不关闭它的 InputStream
仅使用 URL
对象打开另一个 InputStream
并将其返回给客户端(该客户端)可以关闭coruse,但我们以第一个流未结束的方式结束。
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).
那么,是ClassLaoder.getResourceAsStream是否因资源泄漏而破裂?
实用方面:我在中使用
阻止,并且在生产中仍然存在未封闭的资源问题,文件名每30秒加载一次。更多,所有资源都在垃圾收集时关闭,这与 getResourceAsStream
尝试使用资源 finalize()
中的文件流 close()
一致方法。
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.
推荐答案
我做了一个简单的测试程序来验证实际行为:
I made a simple test program to verify the actual behavior:
System.out.println(System.getProperty("java.version"));
URL testURL = new URL("test", null, 0, "/", new URLStreamHandler() {
protected URLConnection openConnection(URL u) throws IOException {
System.out.println("creating connection to "+u);
return new URLConnection(u) {
InputStream is;
public void connect(){}
@Override
public InputStream getInputStream() throws IOException {
System.out.println("getInputStream() for "+u);
if(is==null) is=new InputStream() {
boolean open=true;
@Override
public void close() throws IOException {
if(!open) return;
System.out.println("One InputStream for "+u+" closed");
open=false;
}
public int read() { return -1; }
};
else System.out.println("COULD be shared");
return is;
}
};
}
});
System.out.println("\n trying new ClassLoader");
try(URLClassLoader newlClassLoader=new URLClassLoader(new URL[]{ testURL });
InputStream is=newlClassLoader.getResourceAsStream("foo")) {}
System.out.println("\n trying System ClassLoader");
try {
Method m=URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
m.setAccessible(true);
m.invoke(ClassLoader.getSystemClassLoader(), testURL);
} catch(Exception ex) { ex.printStackTrace(); }
try(InputStream is=ClassLoader.getSystemResourceAsStream("foo")) {}
System.out.println("\n trying bootstrap ClassLoader");
try {
Method m=ClassLoader.class.getDeclaredMethod("getBootstrapClassPath");
m.setAccessible(true);
Object bootstrap = m.invoke(null);
m=bootstrap.getClass().getDeclaredMethod("addURL", URL.class);
m.setAccessible(true);
m.invoke(bootstrap, testURL);
} catch(Exception ex) { ex.printStackTrace(); }
try(InputStream is=ClassLoader.getSystemClassLoader().getResourceAsStream("foo")) {}
在我的机器上使用(测试时使用 1.8.0_05
, 1.8.0_20
和 1.8.0_40
)它已打印
on my machine using (tested with 1.8.0_05
, 1.8.0_20
and 1.8.0_40
) it printed
trying new ClassLoader
creating connection to test:/foo
getInputStream() for test:/foo
One InputStream for test:/foo closed
creating connection to test:/foo
getInputStream() for test:/foo
One InputStream for test:/foo closed
trying System ClassLoader
creating connection to test:/foo
getInputStream() for test:/foo
One InputStream for test:/foo closed
creating connection to test:/foo
getInputStream() for test:/foo
One InputStream for test:/foo closed
trying bootstrap ClassLoader
creating connection to test:/foo
getInputStream() for test:/foo
creating connection to test:/foo
getInputStream() for test:/foo
One InputStream for test:/foo closed
所以从这个测试,我可以得出结论,资源确实打开了两次,但也正确关闭了通过用户类路径访问的所有资源和额外的 ClassLoader
s,因此没有资源泄漏这些情况。
So from this test, I can conclude that the resources are indeed opened twice but also correctly closed for all resources accessed via user class path and additional ClassLoader
s, so there’s no resource leak in these cases.
关于引导程序资源行为的代码分析是正确的,存在资源泄漏但通常这不会发生在应用程序所需的资源上,因为这些应该是可通过用户类路径访问。 ClassLoader
首先尝试他们的父母,但是你的资源不应该在 bootstrap类路径中找到,因此该尝试应该返回 null
并且不打开任何资源。
Your code analysis regarding the bootstrap resource behavior is correct, there is a resource leak but usually this doesn’t occur for resources required by your application as these should be accessible via user class path. ClassLoader
s try their parents first but your resource shouldn’t be found in the bootstrap class path, hence that attempt should return null
and not open any resource.
因此,确保通过JRE的引导类路径无法访问特定于应用程序的资源至关重要,例如不要操纵引导类路径,也不要将资源放入JRE的扩展目录中。这也适用于上面的测试代码,如果您更改测试的顺序,即首先修补引导类路径,所有测试都将显示泄漏,因为所有查找首先尝试其父项,结束于引导加载程序。
So it’s crucial to ensure that application specific resources are not accessible via the JRE’s bootstrap class path, e.g. don’t manipulate the bootstrap class path and don’t put resources into the JRE’s extension directories. This applies also to the test code above, if you change the order of the tests, i.e. patch the bootstrap class path first, all tests will show a leak as all lookups try their parent first, ending at the bootstrap loader.
这篇关于JDK ClassLoader.getResourceAsStream坏了吗? (未公开的资源)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!