JDK-6487095 : URLClassLoader fails to reload resources from dynamically changed jar-files
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.net
  • Affected Version: 6
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2006-10-27
  • Updated: 2011-02-16
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.6.0-beta2"
Java(TM) SE Runtime Environment (build 1.6.0-beta2-b86)
Java HotSpot(TM) Client VM (build 1.6.0-beta2-b86, mixed mode, sharing)

java version "1.5.0_03"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_03-b07)
Java HotSpot(TM) Client VM (build 1.5.0_03-b07, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]

A DESCRIPTION OF THE PROBLEM :
In the context of loading and reloading classes and resources from local jar-files that change overtime (e.g. plug-ins running on some application server) , the URLClassLoader behaves differently whether loading classes or resources (e.g. settings.properties).

For classes, a newly instantiated URLClassLoader will always and correctly load the requested class from the actual jar-file.

For resources, a newly instantiated URLCLassLoader will mostly fail to load the resource (exception) or load a corrupted resource because it keeps a reference to the offset inside the jar-file and attempts to load the resource from the new jar-file using the offset that was valid for the old (the first ever loaded) jar-file.

One aspect of the problem is that the URLClassLoader does not use the same mechanism for loaded class-file than for loading resource files; for the former no cached information is used, for the latter an attempt to use cached information is made.

The other aspect of the problem is that the attempt to use the cache fails because of an probable inconsistency of the cache mechanism for jar-files: it caches positions but not contents, but never checks that the file is still the one for which the positions have been cached.

The end effect is that if resources have moved inside the jar-file from one revision to the other, these resources are not accessible (exception or corruption) anymore until the JVM is restarted.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Attached are two small programs to reproduce the problem. The first shows the problem on the level of the URLClassLoader. The second directly on the level of the jar-file. In both cases, the workaround consisting in disabling the cachine for URL protocols works.


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Created file=[c:\temp\temp.zip]
Read from resource: Simple contents
Created file=[c:\temp\temp.zip]
Read from resource: With a much longer contents for the resource-files.

ACTUAL -
Created file=[c:\temp\temp.zip]
Read from resource: Simple contents
Created file=[c:\temp\temp.zip]
Exception in thread "main" java.util.zip.ZipException: invalid stored block lengths
	at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:140)
	at java.io.FilterInputStream.read(FilterInputStream.java:111)
	at sun.nio.cs.StreamDecoder$CharsetSD.readBytes(StreamDecoder.java:411)
	at sun.nio.cs.StreamDecoder$CharsetSD.implRead(StreamDecoder.java:453)
	at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:183)
	at java.io.InputStreamReader.read(InputStreamReader.java:167)
	at java.io.BufferedReader.fill(BufferedReader.java:136)
	at java.io.BufferedReader.readLine(BufferedReader.java:299)
	at java.io.BufferedReader.readLine(BufferedReader.java:362)
	at BugJarFileCaching.main(BugJarFileCaching.java:54)


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class BugURLClassLoader {
  
  private final static String RESOURCE_NAME = "resource";
  
  public static void main(String[] args) throws Exception {
    System.out.println("Java version=[" + System.getProperty("java.version") + "]");

    // Create a zip file with a file called resource1.txt and resource2.txt in it
    File f = createZipFile("Simple contents");
    // Workaround
//    URLConnection urlConn = f.toURL().openConnection();
//    urlConn.setDefaultUseCaches(false);
    // End workaround

    // Create classloader that uses the zip file
    ClassLoader classLoader = new URLClassLoader(new URL[] {f.toURL()});
    
    // Load resource.txt from the zip file using the classloader
    InputStream is = classLoader.getResourceAsStream(RESOURCE_NAME+"2.txt");
    BufferedReader br = new BufferedReader(new InputStreamReader(is));
    System.out.println("Read from resource: " + br.readLine());
    br.close();
    
    // Recreate a new version of the file
    f = createZipFile("With a much longer contents for the resource-files.");
    
    // Create classloader that uses the zip file
    classLoader = new URLClassLoader(new URL[] {f.toURL()});
    
    // Load resource.txt from the zip file using the classloader
    is = classLoader.getResourceAsStream(RESOURCE_NAME+"2.txt");
    br = new BufferedReader(new InputStreamReader(is));
    System.out.println("Read from resource: " + br.readLine());
    br.close();
  }
  
  private static File createZipFile(String resourceContents) throws IOException {
    // Create a Jar/Zip file with a single entry
    File f = new File("temp.zip");
    FileOutputStream fos = new FileOutputStream(f);
    ZipOutputStream zos = new ZipOutputStream(fos);
    ZipEntry ze = new ZipEntry(RESOURCE_NAME+"1.txt");
    zos.putNextEntry(ze);
    OutputStreamWriter osw = new OutputStreamWriter(zos);
    osw.write(resourceContents);
    osw.flush();
    zos.closeEntry();
    ze = new ZipEntry(RESOURCE_NAME+"2.txt");
    zos.putNextEntry(ze);
    osw = new OutputStreamWriter(zos);
    osw.write(resourceContents);
    osw.flush();
    zos.closeEntry();
    osw.close();
    System.out.println("Created file=[" + f.getAbsolutePath() + "]");
    return f;
  }
}



import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

public class BugJarFileCaching {
  
  private final static String RESOURCE_NAME = "resource";
  
  public static void main(String[] args) throws Exception {
    System.out.println("Java version=[" + System.getProperty("java.version") + "]");
    
    // Create a zip file with a file called resource1.txt and resource2.txt in it
    File f = createZipFile("Simple contents");
    
    // Load resource.txt from the zip going over the URL (not doing so works fine)
    ZipFile zf = new ZipFile(f);
    ZipEntry ze = zf.getEntry(RESOURCE_NAME+"2.txt");
    File f2 = new File(zf.getName()+"!/"+ze.getName());
    URL url = f2.toURI().toURL();
    url = new URL("jar",url.getHost(),url.toString());
    // Workaround
//    URLConnection urlConn = url.openConnection();
//    urlConn.setDefaultUseCaches(false);
    // End workaround
    InputStream is = url.openStream();
    if (is == null) System.exit(0);
    BufferedReader br = new BufferedReader(new InputStreamReader(is));
    System.out.println("Read from resource: " + br.readLine());
    br.close();
    
    // Recreate a new version of the file.
    // By removing the dot at the end, you can avoid the exception and see
    // the corruption effect.
    f = createZipFile("With a much longer contents for the resource-files.");
    
    // Load resource.txt from the zip going over the URL (not doing so works fine)
    zf = new ZipFile(f);
    ze = zf.getEntry(RESOURCE_NAME+"2.txt");
    f2 = new File(zf.getName()+"!/"+ze.getName());
    url = f2.toURI().toURL();
    url = new URL("jar",url.getHost(),url.toString());
    is = url.openStream();
    br = new BufferedReader(new InputStreamReader(is));
    System.out.println("Read from resource: " + br.readLine());
    br.close();
  }
  
  private static File createZipFile(String resourceContents) throws IOException {
    // Create a Jar/Zip file with a single entry
    File f = new File("c:\\temp\\temp.zip");
    FileOutputStream fos = new FileOutputStream(f);
    ZipOutputStream zos = new ZipOutputStream(fos);
    ZipEntry ze = new ZipEntry(RESOURCE_NAME+"1.txt");
    zos.putNextEntry(ze);
    OutputStreamWriter osw = new OutputStreamWriter(zos);
    osw.write(resourceContents);
    osw.flush();
    zos.closeEntry();
    ze = new ZipEntry(RESOURCE_NAME+"2.txt");
    zos.putNextEntry(ze);
    osw = new OutputStreamWriter(zos);
    osw.write(resourceContents);
    osw.flush();
    zos.closeEntry();
    osw.close();
    System.out.println("Created file=[" + f.getAbsolutePath() + "]");
    return f;
  }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
See source code of executable test case.