A DESCRIPTION OF THE PROBLEM :
I found multiple bug reports more or less related to this issue (JDK-8232854, JDK-6896088, JDK-8239054), but I think none are exactly the same. The only bugs that IMO were the same are JDK-8156014 and JDK-8013099, but they have been resolved after changing the ServiceLoader implementation. I think the original issue is still there.
When creating an URLClassLoader, some operations like getResources will lead to the creation of a JarFile in the global cache, that will never be properly released. As a result, the JAR file can't be deleted on Windows.
URLClassloader tries to keep track of open files that need to be closed, but only when calling getResourceAsStream. Calling getResource or getResources will "bypass" this.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Creates an arbitrary JAR file with a resource
> ls
Main.java Main.class Hello.txt
> jar cvf sample.jar Main.class Hello.txt
2. Run the Reproducer class. It does the following:
a. Creates URLClassLoader and uses it to load a resource from the JAR
b. Calls close()
c. Attempts to delete the JAR, but can not.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The jar file can be deleted after closing the classloader
ACTUAL -
Can't delete the file, as it was cached in sun.net.www.protocol.jar.JarFileFactory
---------- BEGIN SOURCE ----------
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
public class Reproducer {
final static Path JAR_FILE = Paths.get("sample.jar");
public static void main(String[] args) throws IOException {
var copy = Files.copy(JAR_FILE, Files.createTempFile("copysample", "jar"), StandardCopyOption.REPLACE_EXISTING);
try (var classLoader = new URLClassLoader(new URL[]{copy.toUri().toURL()})) {
var resource = classLoader.getResource("Hello.txt");
try (var conn = resource.openConnection().getInputStream()) {
System.out.println(new String(conn.readAllBytes()));
}
}
Files.delete(copy); // Fails on Windows
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
// Manually get the JarFile from the cache and close it:
var url = new URL("jar:" + copy.toUri().toURL() + "!/");
var jarConnection = (JarURLConnection) url.openConnection();
var jarFile = jarConnection.getJarFile();
jarFile.close();
FREQUENCY : always
I found multiple bug reports more or less related to this issue (
When creating an URLClassLoader, some operations like getResources will lead to the creation of a JarFile in the global cache, that will never be properly released. As a result, the JAR file can't be deleted on Windows.
URLClassloader tries to keep track of open files that need to be closed, but only when calling getResourceAsStream. Calling getResource or getResources will "bypass" this.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Creates an arbitrary JAR file with a resource
> ls
Main.java Main.class Hello.txt
> jar cvf sample.jar Main.class Hello.txt
2. Run the Reproducer class. It does the following:
a. Creates URLClassLoader and uses it to load a resource from the JAR
b. Calls close()
c. Attempts to delete the JAR, but can not.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The jar file can be deleted after closing the classloader
ACTUAL -
Can't delete the file, as it was cached in sun.net.www.protocol.jar.JarFileFactory
---------- BEGIN SOURCE ----------
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
public class Reproducer {
final static Path JAR_FILE = Paths.get("sample.jar");
public static void main(String[] args) throws IOException {
var copy = Files.copy(JAR_FILE, Files.createTempFile("copysample", "jar"), StandardCopyOption.REPLACE_EXISTING);
try (var classLoader = new URLClassLoader(new URL[]{copy.toUri().toURL()})) {
var resource = classLoader.getResource("Hello.txt");
try (var conn = resource.openConnection().getInputStream()) {
System.out.println(new String(conn.readAllBytes()));
}
}
Files.delete(copy); // Fails on Windows
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
// Manually get the JarFile from the cache and close it:
var url = new URL("jar:" + copy.toUri().toURL() + "!/");
var jarConnection = (JarURLConnection) url.openConnection();
var jarFile = jarConnection.getJarFile();
jarFile.close();
FREQUENCY : always