Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8075235

ClassLoader.getResourceAsStream() opens 2 streams, returning 1, not closing anot

XMLWordPrintable

      FULL PRODUCT VERSION :


      ADDITIONAL OS VERSION INFORMATION :
      Windows 7 64-bit

      A DESCRIPTION OF THE PROBLEM :
      Nicely formatted descirption: http://stackoverflow.com/questions/28496044/is-jdk-classloader-getresourceasstream-broken-unclosed-resources

       1 down vote favorite


      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, 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 user 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?


      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Use ClassLoader.getResourceAsStream() to load a file, IN LOOP.

      Close received stream on client side, but JDK silently opens another unclosed InputStream = RESOURCE LEAK

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Internal InputStream that is wrapped in Resource only to get URL internally in ClassLoader should be closed.

      REPRODUCIBILITY :
      This bug can be reproduced always.

      CUSTOMER SUBMITTED WORKAROUND :
      Do no use getResourceAsStream() for loading files repeatedly.

            Unassigned Unassigned
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated:
              Resolved: