英文:
Cannot delete external JAR file after loading in Image
问题
在这个简单的JavaFX应用程序中,我正在忙于构建一个应用程序,您可以在其中添加和删除.jar文件,我正在从这些文件中收集一些信息,其中一些信息是以图像形式存储的纹理。
每当我使用来自jar文件的路径加载图像时,后来我无法删除这个.jar文件。这是很合理的,因为.jar文件现在正在被这个图像使用,但由于某种原因,即使在删除对这个图像的所有引用并调用垃圾收集器后,我也无法从应用程序内部删除.jar文件。
我目前尝试删除文件的方式如下:
File file = new File("mods/file.jar");
file.delete();
在应用程序的某个时刻,我如下初始化图像:
Image block = new Image(pathToJar);
我尝试了以下几种方法来解决我的问题:
- 设置
block = null;
并调用System.gc();
- 调用
Image.cancel()
并调用System.gc();
- 我还尝试了与
System.runFinalization();
结合使用上述方法 - 我尝试在舞台关闭时删除.jar文件,希望所有内容都会被卸载,但没有成功。
目前,我不太确定为什么无法实际删除.jar文件。有人能否解释一下为什么会这样,并如何解决这个问题?非常感谢!
英文:
I am currently busy with building a simple javafx application. In this application, you can add and remove .jar files from within the application, from which I am gathering some information. One of the things I use from these .jar files are textures stored as images.
Whenever I load an Image using a path from the jar file, I am unable to remove this .jar file later on. This is quite logical as the .jar file is now being used by this Image, but for some reason, I am unable to delete the .jar file from within the application even when removing all references to this Image and calling the garbage collector.
The way I am currently trying to delete the file is the following:
File file = new File("mods/file.jar");
file.delete();
At some point in the application I initialize the image as follows:
Image block = new Image(pathToJar);
I have tried the following things to resolve my problem:
- Set
block = null;
and callSystem.gc();
- Call
Image.cancel()
and callSystem.gc();
- I have also tried the above in combination with
System.runFinalization();
- I tried removing the .jar file when the stage is closing in hopes of everything being unloaded, but without success.
At this point in time, I am not quite sure why I am unable to actually delete the .jar file. Could anyone possibly explain to me why this is the case and how it could be resolved? Many thanks in advance!
EDIT: the purpose of the application was not clear enough. I am making a color picker that takes average colors of textures from .jar files (Minecraft mods). These .jar files are to be added and deleted from the application in a flexible way. Once you do not want to consider textures of a certain .jar file anymore, you just remove it from the application and be done with it. For ease of use, I made copies of the .jar files so I can access them relatively. Once you are done with the .jar file I want to remove this copy as it will just take in unnecessary storage.
I have narrowed the problem (thus far) down to the initialization of an Image. On request, here a MRE:
public class example extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
// Read image, needed at some point in my code
Image block = new Image("jar:File:./mods/botania.jar!/assets/botania/textures/blocks/alfheim_portal.png");
// Make it so that jar is not used anymore
block.cancel();
block = null;
System.gc();
// Try to delete the file after being done with it
File delete = new File("mods/botania.jar");
Files.delete(delete.toPath());
}
}
After initializing the image, and thereafter removing references to it, it should be cleaned by the garbage collector (at least to my knowledge). Instead, now it just gives the following error:
> Caused by: java.nio.file.FileSystemException: mods\botania.jar: The
> process cannot access the file because it is being used by another
> process.
答案1
得分: 4
无法在我的系统上重现此问题(在Mac OS X上使用Java 14);然而,在Windows 10上运行相同的JDK/JavaFX版本会产生您描述的异常。
问题似乎是(请参阅https://stackoverflow.com/a/54777849/2067492,并向@matt表示感谢,他挖掘出了这一点),默认情况下,jar:
URL的URL处理程序缓存对底层JarFile
对象的访问(即使从中获取的InputStream
已关闭,也不会调用close()
)。由于Windows在这些情况下保持文件句柄,底层操作系统无法删除jar文件。
我认为解决这个问题的最自然的方式是使用java.util.jar
API(或者只是普通的java.util.zip
API),它将允许您更好地控制关闭资源,而不是生成URL并将URL传递给Image
构造函数。
以下是使用这种方法的自包含示例,在我的Mac和Windows 10虚拟VM上都有效:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import javax.imageio.ImageIO;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void init() throws Exception {
// 在一个jar文件中创建一张图片:
BufferedImage image = new BufferedImage(100, 100, ColorSpace.TYPE_RGB);
Graphics graphics = image.getGraphics();
graphics.setColor(new Color(0xd5, 0x5e, 0x00));
graphics.fillRect(0, 0, 100, 100);
graphics.setColor(new Color(0x35, 0x9b, 0x73));
graphics.fillOval(25, 25, 50, 50);
JarOutputStream out = new JarOutputStream(new FileOutputStream("test.jar"));
ZipEntry entry = new ZipEntry("test.png");
out.putNextEntry(entry);
ImageIO.write(image, "png", out);
out.close();
}
@Override
public void start(Stage stage) throws Exception {
assert Files.exists(Paths.get("test.jar")) : "Jar文件未创建";
JarFile jarFile = new JarFile("test.jar");
JarEntry imgEntry = jarFile.getJarEntry("test.png");
InputStream inputStream = jarFile.getInputStream(imgEntry);
Image img = new Image(inputStream);
jarFile.close();
// 以下行(而不是前面的五行)在Mac OS X上工作,但在Windows上不工作
// Image img = new Image("jar:file:test.jar!/test.png");
Files.delete(Paths.get("test.jar"));
assert !Files.exists(Paths.get("test.jar")) : "Jar文件未删除";
ImageView iview = new ImageView(img);
Scene scene = new Scene(new BorderPane(iview), 200, 200);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
英文:
I can't reproduce the issue on my system (Java 14 on Mac OS X); however running it on Windows 10 with the same JDK/JavaFX versions produces the exception you describe.
The issue appears to be (see https://stackoverflow.com/a/54777849/2067492, and hat-tip to @matt for digging that out) that by default the URL handler for a jar:
URL caches access to the underlying JarFile
object (and doesn't call close()
, even if an InputStream
obtained from it is closed). Since Windows holds on to file handles in those situations, the underlying OS is unable to delete the jar file.
I think the most natural way to address this is to use the java.util.jar
API (or just the plain java.util.zip
API), which will give you far more control over closing resources than generating a URL and passing the URL to the Image
constructor.
Here's a self-contained example using this approach, which works both on my Mac and on my Windows 10 virtual VM:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import javax.imageio.ImageIO;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void init() throws Exception {
// create an image in a jar file:
BufferedImage image = new BufferedImage(100, 100, ColorSpace.TYPE_RGB);
Graphics graphics = image.getGraphics();
graphics.setColor(new Color(0xd5, 0x5e, 0x00));
graphics.fillRect(0, 0, 100, 100);
graphics.setColor(new Color(0x35, 0x9b, 0x73));
graphics.fillOval(25, 25, 50, 50);
JarOutputStream out = new JarOutputStream(new FileOutputStream("test.jar"));
ZipEntry entry = new ZipEntry("test.png");
out.putNextEntry(entry);
ImageIO.write(image, "png", out);
out.close();
}
@Override
public void start(Stage stage) throws Exception {
assert Files.exists(Paths.get("test.jar")) : "Jar file not created";
JarFile jarFile = new JarFile("test.jar");
JarEntry imgEntry = jarFile.getJarEntry("test.png");
InputStream inputStream = jarFile.getInputStream(imgEntry);
Image img = new Image(inputStream);
jarFile.close();
// The following line (instead of the previous five lines) works on Mac OS X,
// but not on Windows
//
// Image img = new Image("jar:file:test.jar!/test.png");
Files.delete(Paths.get("test.jar"));
assert ! Files.exists(Paths.get("test.jar")) : "Jar file not deleted";
ImageView iview = new ImageView(img);
Scene scene = new Scene(new BorderPane(iview), 200, 200);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论