0 java garbage-collection javafx image render
我有一个 JavaFX 程序,在循环中使用 setImage(new Image(new ByteArrayInputStream(imageBytes))) 方法在 ImageView 中连续设置图像。整个过程被包装在 Platform.runLater() 中以确保 UI 线程安全。
我面临的问题是,当我启用 -Dprism.verbose=true 时,控制台会连续打印消息“Growing pool D3D Vram Pool target”,然后显示不断增加的数字。最终,它会导致 NullPointerException。令人惊讶的是,在 setImage 调用之后添加 System.gc() 解决了该问题,并且异常在一段时间后停止。
据我所知,出于性能原因,不建议在循环中调用 System.gc(),并且垃圾收集器应该自动处理此问题。但是,如果没有这种显式调用,不断增长的池和异常仍然存在。
我还尝试在设置实际图像之前调用 setImage(null) 以通知垃圾收集器,但这没有任何区别。我什至增加了内存,但问题仍然存在。
这种行为正常吗?是什么导致池不断增长和 NullPointerException?关于如何在不依赖 System.gc() 的情况下解决此问题有什么建议吗?
(This code constantily grows D3D Vram Pool like : Growing pool D3D Vram Pool target to 524.167.168 Growing pool D3D Vram Pool target to 524.167.168 Growing pool D3D Vram Pool target to 535.226.368..)
几秒钟后我得到:
java.lang.NullPointerException at com.sun.prism.d3d.D3DTexture.getContext(D3DTexture.java:84) at com.sun.prism.d3d.D3DTexture.update(D3DTexture.java:207) at com.sun.prism.d3d.D3DTexture.update(D3DTexture.java:151)...
this.packetImage.setImageByteStream(new ImageByteStream() {
@Override
public void stream(byte[] imageBytes) {
Platform.runLater(() -> {
imageView.setImage(new Image(new ByteArrayInputStream(imageBytes)));
System.gc(); //<- fixes the problem
});
}
});
Run Code Online (Sandbox Code Playgroud)
我没有办法重现您的确切问题,并且此答案中的信息可能无法解决您的问题,但也许会有所帮助。
一般建议
你不想做的是编写一些繁忙的循环:
如果您重复加载相同的图像,您可能需要缓存加载的图像(此答案中有一个示例缓存实现),因此您不需要再次加载它们。如果渲染图像中不需要完整的图像分辨率,您还需要在图像构造函数中适当调整图像的大小。
正如 Trashgod 在评论中建议的那样,在后台加载图像通常是一个好主意。
如果从 URL 加载,您可以通过适当的图像构造函数在后台加载图像(并调整大小)。如果这样做,您就不需要对任务或服务进行多线程处理。如果您需要知道的话,您可以监听进度属性来查看加载的进度。
您可以使用 Timeline 或 PauseTransition定期加载新图像,在后台加载它们以确保主 JavaFX 线程不受影响。
例子
此示例使用 Task 和 ScheduledService 在后台线程中定期加载图像。
这是一个在后台从字节数组加载图像的任务。
提供此选项只是因为您不是从 URL 加载,而是从 ByteArrayInputStream 加载,而 JavaFX 20 Image API 中没有后台加载构造函数。
import javafx.concurrent.Task;
import javafx.scene.image.Image;
import java.io.ByteArrayInputStream;
class ImageLoadingTask extends Task<Image> {
private final byte[] imageBytes;
public ImageLoadingTask(byte[] imageBytes) {
this.imageBytes = imageBytes;
}
@Override
protected Image call() throws Exception {
Image image = new Image(
new ByteArrayInputStream(
imageBytes
)
);
if (image.isError()) {
throw image.getException();
}
return image;
}
}
Run Code Online (Sandbox Code Playgroud)
你说你正在循环加载图像。我不太确定你为什么使用循环。但我猜你正在尝试定期加载图像。为此,对于上面定义的任务,您可以使用 ScheduledService。
我不确定您在哪里以及如何获取图像的字节。因此,对于这个示例,为了简单起见,我只是在预加载列表中传递所有图像字节。对于具有动态图像源的实际应用程序实现,您不会希望这样做。
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.util.Duration;
import java.util.List;
class ImageLoadingService extends ScheduledService<Image> {
private static final Duration SHOW_IMAGE_PERIOD = Duration.seconds(2);
private final List<byte[]> imageByteList;
private int nextImageIdx = 0;
public ImageLoadingService(
ImageView imageView,
List<byte[]> imageByteList
) {
this.imageByteList = imageByteList;
setPeriod(SHOW_IMAGE_PERIOD);
setRestartOnFailure(false);
configureToLoadNextImage();
valueProperty().addListener((observable, oldValue, newValue) -> {
if (newValue != null && !newValue.isError()) {
imageView.setImage(
getValue()
);
configureToLoadNextImage();
}
});
exceptionProperty().addListener((observable, oldException, imageLoadingException) -> {
if (imageLoadingException != null) {
imageLoadingException.printStackTrace();
}
});
}
private final ObjectProperty<byte[]> imageBytes = new SimpleObjectProperty<>(
this,
"imageBytes"
);
public final void setImageBytes(byte[] value) {
imageBytes.set(value);
}
public final byte[] getImageBytes() {
return imageBytes.get();
}
public final ObjectProperty<byte[]> imageBytesProperty() {
return imageBytes;
}
@Override
protected Task<Image> createTask() {
return new ImageLoadingTask(
getImageBytes()
);
}
private void configureToLoadNextImage() {
setImageBytes(imageByteList.get(nextImageIdx));
nextImageIdx = (nextImageIdx + 1) % imageByteList.size();
}
}
Run Code Online (Sandbox Code Playgroud)
演示服务和任务的使用的示例应用程序。它将定期从字节数组加载后台图像并将其显示在 ImageView 中,以创建幻灯片。
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.List;
public class BackgroundImageLoader extends Application {
private static final int IMAGE_SIZE = 96;
private final List<byte[]> monsterImages = MonsterImageCreator.createMonsterImageBytes();
@Override
public void start(Stage stage) throws IOException {
ImageView imageView = new ImageView();
ImageLoadingService imageLoadingService = new ImageLoadingService(
imageView,
monsterImages
);
imageLoadingService.start();
StackPane layout = new StackPane(imageView);
layout.setPrefSize(IMAGE_SIZE, IMAGE_SIZE);
stage.setScene(new Scene(layout));
stage.show();
}
}
Run Code Online (Sandbox Code Playgroud)
用于从图像资源创建一些测试数据的实用程序类。
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
class MonsterImageCreator {
private enum Monster {
Medusa,
Dragon,
Treant,
Unicorn
}
public static List<byte[]> createMonsterImageBytes() {
List<byte[]> imageBytes = new ArrayList<>(
Monster.values().length
);
for (Monster monster : Monster.values()) {
try (InputStream inputStream = Objects.requireNonNull(
MonsterImageCreator.class.getResource(
monster + "-icon.png"
)
).openStream()) {
byte[] bytes = inputStream.readAllBytes();
imageBytes.add(
bytes
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return imageBytes;
}
}
Run Code Online (Sandbox Code Playgroud)
此答案中提供了此处使用的图像:
图像应放置在与您放置 MonsterImageCreator 的包匹配的资源目录中。
一些补充要点
我还增加了内存
你真的不能那样做。Direct3D 纹理是 JavaFX 实现所使用的 3D 图形系统的本机资源,您无法控制内存的使用方式。使用的内存可以是显卡显存中的纹理内存,而不是与 Java 堆相关的内存。
您正在做的是以某种方式炸毁视频内存空间。我怎么不知道。
是什么导致池不断增长
分配新的图形纹理是 UI 应用程序中的常见操作。纹理内存池的大小动态变化是正常的。纹理应用广泛,不仅用于图像,还用于渲染引擎(2D 和 3D 模式)。
一般来说,您实际上不必担心纹理内存资源的管理,因为 JavaFX 实现将为您处理这项工作,就像您不需要太担心 Java 应用程序中的内存管理一样。
您真正需要担心的唯一事情是在 Java 中管理内存资源时必须考虑的标准事情。这些是这样的: