Otro caso de esquina en el que esto podría suceder: si lee / escribe un archivo JAR a través de a URL
y luego intenta eliminar el mismo archivo dentro de la misma sesión de 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 razón es que la lógica de manejo de archivos JAR interno de Java tiende a almacenar en caché las JarFile
entradas:
// 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();
}
}
}
}
Y cada uno JarFile
(más bien, la ZipFile
estructura subyacente ) mantendría un identificador para el archivo, desde el momento de la construcción hasta que close()
se invoca:
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;
Hay una buena explicación sobre este problema de NetBeans .
Aparentemente, hay dos formas de "arreglar" esto:
Puede deshabilitar el almacenamiento en caché de archivos JAR, para el actual URLConnection
o para todos los correos URLConnection
electrónicos futuros (globalmente) en la sesión actual de JVM:
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);
[ADVERTENCIA DE HACK!] Puede purgar manualmente el JarFile
de la caché cuando haya terminado con él. El administrador de caché sun.net.www.protocol.jar.JarFileFactory
es un paquete privado, pero algo de magia de reflexión puede hacer el trabajo por usted:
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.
Tenga en cuenta: Todo esto se basa en el código base de Java 8 ( 1.8.0_144
); es posible que no funcionen con otras versiones o versiones posteriores.