Pio*_*ler 5 java resources classloader resource-leak java-8
我将尝试证明这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;
}
}
Run Code Online (Sandbox Code Playgroud)
如果我们将close()
在InputStream
提供这种方式,我们将关闭只有流中打开url.openStream()
.JDK来源:
public final InputStream openStream() throws java.io.IOException {
return openConnection().getInputStream();
}
Run Code Online (Sandbox Code Playgroud)
我想,问题是,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;
}
Run Code Online (Sandbox Code Playgroud)
而现在,在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
}
Run Code Online (Sandbox Code Playgroud)
为什么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;
}
Run Code Online (Sandbox Code Playgroud)
为什么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();
}
};
}
Run Code Online (Sandbox Code Playgroud)
我们可以看到openConnection()
并且getInputStream()
,它们没有被关闭,并且回退所有返回的调用Resource
我们最终只使用getURL()
包装的方法Resource
而不关闭它InputStream
只是使用该URL
对象打开另一个InputStream
并将其返回给客户端(客户端可以关闭)腐败,但我们以第一流未封闭结束).
那么,ClassLaoder.getResourceAsStream是否因泄漏资源而破裂?
实用方面:我getResourceAsStream
在try-with-resources
块中使用,并且在生产中仍然存在未封闭的资源问题,文件名每30秒加载一次.更多的,所有的资源都关闭了垃圾收集,这与文件流相一致close()
的finalize()
方法.
我做了一个简单的测试程序来验证实际行为:
\n\nSystem.out.println(System.getProperty("java.version"));\nURL testURL = new URL("test", null, 0, "/", new URLStreamHandler() {\n protected URLConnection openConnection(URL u) throws IOException {\n System.out.println("creating connection to "+u);\n return new URLConnection(u) {\n InputStream is;\n public void connect(){}\n @Override\n public InputStream getInputStream() throws IOException {\n System.out.println("getInputStream() for "+u);\n if(is==null) is=new InputStream() {\n boolean open=true;\n @Override\n public void close() throws IOException {\n if(!open) return;\n System.out.println("One InputStream for "+u+" closed");\n open=false;\n }\n public int read() { return -1; }\n };\n else System.out.println("COULD be shared");\n return is;\n }\n };\n }\n});\nSystem.out.println("\\n trying new ClassLoader");\ntry(URLClassLoader newlClassLoader=new URLClassLoader(new URL[]{ testURL });\n InputStream is=newlClassLoader.getResourceAsStream("foo")) {}\n\nSystem.out.println("\\n trying System ClassLoader");\ntry {\n Method m=URLClassLoader.class.getDeclaredMethod("addURL", URL.class);\n m.setAccessible(true);\n m.invoke(ClassLoader.getSystemClassLoader(), testURL);\n} catch(Exception ex) { ex.printStackTrace(); }\ntry(InputStream is=ClassLoader.getSystemResourceAsStream("foo")) {}\n\nSystem.out.println("\\n trying bootstrap ClassLoader");\ntry {\n Method m=ClassLoader.class.getDeclaredMethod("getBootstrapClassPath");\n m.setAccessible(true);\n Object bootstrap = m.invoke(null);\n m=bootstrap.getClass().getDeclaredMethod("addURL", URL.class);\n m.setAccessible(true);\n m.invoke(bootstrap, testURL);\n} catch(Exception ex) { ex.printStackTrace(); }\n\ntry(InputStream is=ClassLoader.getSystemClassLoader().getResourceAsStream("foo")) {}\n
Run Code Online (Sandbox Code Playgroud)\n\n在我的机器上使用(用1.8.0_05
、1.8.0_20
和测试1.8.0_40
)它打印
System.out.println(System.getProperty("java.version"));\nURL testURL = new URL("test", null, 0, "/", new URLStreamHandler() {\n protected URLConnection openConnection(URL u) throws IOException {\n System.out.println("creating connection to "+u);\n return new URLConnection(u) {\n InputStream is;\n public void connect(){}\n @Override\n public InputStream getInputStream() throws IOException {\n System.out.println("getInputStream() for "+u);\n if(is==null) is=new InputStream() {\n boolean open=true;\n @Override\n public void close() throws IOException {\n if(!open) return;\n System.out.println("One InputStream for "+u+" closed");\n open=false;\n }\n public int read() { return -1; }\n };\n else System.out.println("COULD be shared");\n return is;\n }\n };\n }\n});\nSystem.out.println("\\n trying new ClassLoader");\ntry(URLClassLoader newlClassLoader=new URLClassLoader(new URL[]{ testURL });\n InputStream is=newlClassLoader.getResourceAsStream("foo")) {}\n\nSystem.out.println("\\n trying System ClassLoader");\ntry {\n Method m=URLClassLoader.class.getDeclaredMethod("addURL", URL.class);\n m.setAccessible(true);\n m.invoke(ClassLoader.getSystemClassLoader(), testURL);\n} catch(Exception ex) { ex.printStackTrace(); }\ntry(InputStream is=ClassLoader.getSystemResourceAsStream("foo")) {}\n\nSystem.out.println("\\n trying bootstrap ClassLoader");\ntry {\n Method m=ClassLoader.class.getDeclaredMethod("getBootstrapClassPath");\n m.setAccessible(true);\n Object bootstrap = m.invoke(null);\n m=bootstrap.getClass().getDeclaredMethod("addURL", URL.class);\n m.setAccessible(true);\n m.invoke(bootstrap, testURL);\n} catch(Exception ex) { ex.printStackTrace(); }\n\ntry(InputStream is=ClassLoader.getSystemClassLoader().getResourceAsStream("foo")) {}\n
Run Code Online (Sandbox Code Playgroud)\n\n因此,从这个测试中,我可以得出结论,资源确实打开了两次,但对于通过用户类路径和附加访问的所有资源也正确关闭ClassLoader
,因此在这些情况下\xe2\x80\x99s没有资源泄漏。
您关于引导资源行为的代码分析是正确的,存在资源泄漏,但通常应用程序所需的资源不会发生这种情况,因为这些资源应该可以通过用户类路径访问。ClassLoader
首先尝试其父级,但您的资源不应该在引导类路径中找到\xe2\x80\x99 ,因此该尝试应该返回null
并且不打开任何资源。
因此,\xe2\x80\x99s 确保应用程序特定资源无法通过 JRE\xe2\x80\x99s 引导类路径访问至关重要,例如不要\xe2\x80\x99t 操作引导类路径并不要\xe2\x80 \x99t 将资源放入 JRE\xe2\x80\x99s 扩展目录中。这也适用于上面的测试代码,如果您更改测试的顺序,即首先修补引导类路径,则所有测试都将显示泄漏,因为所有查找都会首先尝试其父级,最后在引导加载程序处结束。
\n