Un autre cas de coin où cela pourrait se produire: si vous lisez / écrivez un fichier JAR via un URL
et plus tard, essayez de supprimer le même fichier dans la même session JVM.
File f = new File("/tmp/foo.jar");
URL j = f.toURI().toURL();
URL u = new URL("jar:" + j + "!/META-INF/MANIFEST.MF");
URLConnection c = u.openConnection();
// open a Jar entry in auto-closing manner
try (InputStream i = c.getInputStream()) {
// just read some stuff; for demonstration purposes only
byte[] first16 = new byte[16];
i.read(first16);
System.out.println(new String(first16));
}
// ...
// i is now closed, so we should be good to delete the jar; but...
System.out.println(f.delete()); // says false!
La raison en est que la logique de gestion des fichiers JAR internes de Java a tendance à mettre en cache les JarFile
entrées:
// inner class of `JarURLConnection` that wraps the actual stream returned by `getInputStream()`
class JarURLInputStream extends FilterInputStream {
JarURLInputStream(InputStream var2) {
super(var2);
}
public void close() throws IOException {
try {
super.close();
} finally {
// if `getUseCaches()` is set, `jarFile` won't get closed!
if (!JarURLConnection.this.getUseCaches()) {
JarURLConnection.this.jarFile.close();
}
}
}
}
Et chacun JarFile
(plutôt, la ZipFile
structure sous-jacente ) détiendrait une poignée pour le fichier, dès le moment de la construction jusqu'àclose()
son invocation:
public ZipFile(File file, int mode, Charset charset) throws IOException {
// ...
jzfile = open(name, mode, file.lastModified(), usemmap);
// ...
}
// ...
private static native long open(String name, int mode, long lastModified,
boolean usemmap) throws IOException;
Il y a une bonne explication sur ce problème NetBeans .
Apparemment, il existe deux façons de "résoudre" ce problème:
Vous pouvez désactiver la mise en cache du fichier JAR - pour la session actuelle URLConnection
ou pour tous les futurs URLConnection
(globalement) dans la session JVM actuelle:
URL u = new URL("jar:" + j + "!/META-INF/MANIFEST.MF");
URLConnection c = u.openConnection();
// for only c
c.setUseCaches(false);
// globally; for some reason this method is not static,
// so we still need to access it through a URLConnection instance :(
c.setDefaultUseCaches(false);
[AVERTISSEMENT DE HACK!] Vous pouvez purger manuellement le JarFile
du cache lorsque vous avez terminé. Le gestionnaire de cache sun.net.www.protocol.jar.JarFileFactory
est privé de package, mais une certaine magie de réflexion peut faire le travail pour vous:
class JarBridge {
static void closeJar(URL url) throws Exception {
// JarFileFactory jarFactory = JarFileFactory.getInstance();
Class<?> jarFactoryClazz = Class.forName("sun.net.www.protocol.jar.JarFileFactory");
Method getInstance = jarFactoryClazz.getMethod("getInstance");
getInstance.setAccessible(true);
Object jarFactory = getInstance.invoke(jarFactoryClazz);
// JarFile jarFile = jarFactory.get(url);
Method get = jarFactoryClazz.getMethod("get", URL.class);
get.setAccessible(true);
Object jarFile = get.invoke(jarFactory, url);
// jarFactory.close(jarFile);
Method close = jarFactoryClazz.getMethod("close", JarFile.class);
close.setAccessible(true);
//noinspection JavaReflectionInvocation
close.invoke(jarFactory, jarFile);
// jarFile.close();
((JarFile) jarFile).close();
}
}
// and in your code:
// i is now closed, so we should be good to delete the jar
JarBridge.closeJar(j);
System.out.println(f.delete()); // says true, phew.
Remarque: tout ceci est basé sur la base de code Java 8 ( 1.8.0_144
); ils peuvent ne pas fonctionner avec d'autres versions / versions ultérieures.