JDK ClassLoader.getResourceAsStream坏了吗? (未公开的资源) [英] Is JDK ClassLoader.getResourceAsStream broken? (unclosed resources)

查看:179
本文介绍了JDK ClassLoader.getResourceAsStream坏了吗? (未公开的资源)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我将尝试证明 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()。 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是否因资源泄漏而破裂?

实用方面:我在中使用 getResourceAsStream 尝试使用资源阻止,并且在生产中仍然存在未封闭的资源问题,文件名每30秒加载一次。更多,所有资源都在垃圾收集时关闭,这与 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 ClassLoaders, 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. ClassLoaders 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆