Một trường hợp góc khác mà điều này có thể xảy ra: nếu bạn đọc / ghi một tệp JAR thông qua URL
và sau đó cố gắng xóa cùng một tệp trong cùng một phiên 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!
Lý do là logic xử lý tệp JAR bên trong của Java, có xu hướng lưu JarFile
các mục vào bộ nhớ cache :
// 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();
}
}
}
}
Và mỗi JarFile
(đúng hơn là ZipFile
cấu trúc bên dưới ) sẽ giữ một phần xử lý đối với tệp, ngay từ khi xây dựng cho đến khi close()
được gọi:
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;
Có một lời giải thích tốt về vấn đề NetBeans này .
Rõ ràng có hai cách để "sửa chữa" điều này:
Bạn có thể tắt bộ nhớ đệm tệp JAR - cho hiện tại URLConnection
hoặc cho tất cả các tương lai URLConnection
(trên toàn cầu) trong phiên JVM hiện tại:
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);
[CẢNH BÁO HACK!] Bạn có thể xóa thủ công JarFile
khỏi bộ nhớ cache khi bạn hoàn thành việc này. Trình quản lý bộ nhớ cache sun.net.www.protocol.jar.JarFileFactory
là gói riêng tư, nhưng một số phép thuật phản chiếu có thể hoàn thành công việc cho bạn:
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.
Xin lưu ý: Tất cả điều này dựa trên Java 8 codebase ( 1.8.0_144
); chúng có thể không hoạt động với các phiên bản khác / mới hơn.