I don't have a way to reproduce your exact issue, and the info in this answer may not solve your issue, but perhaps it will help.
General Advice
What you don't want to do is write some busy loop that:
- consumes the JavaFX thread and freezes the application.
- rapidly gobbles up huge amounts of resource memory without allowing the memory management system to appropriately free up stale memory.
- floods the JavaFX runLater queue with more runnables than the system can reasonably process.
- corrupts memory by accessing it on multiple threads.
If you were repetitively loading the same images, you would want to cache the loaded images (there is an example cache implementation in this answer), so you don't need to load them again. You also want to appropriately size the images in the image constructor if you don't need the full image resolution in your rendered images.
As advised by Trashgod in the comments, it is usually a good idea to load Images in the background.
If loading from a URL, you can load (and size) images in the background via the appropriate image constructor. You don't need to multithread with a task or service if you do that. You can listen to the progress property to see how the loading is progressing if you need to know that.
You can periodically load new images using a Timeline or PauseTransition, loading them in the background to ensure that the main JavaFX thread is not impacted.
Example
This example uses a Task and ScheduledService to load images periodically in a background thread.
This is a Task that loads an image from a byte array in the background.
This is only provided because you are not loading from a URL, but from a ByteArrayInputStream for which there is no background loading constructor in the 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;
}
}
You say you are loading the images in a loop. I'm not exactly sure why you use a loop. But I guess you are trying to load images periodically. To do that, for the task defined above, you can use a ScheduledService.
I am not sure where and how you get the bytes for your images. So for this example, I just pass in all of the image bytes in a preloaded List for simplicity. For an actual application implementation with a dynamic source of images, you wouldn't want to do that.
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();
}
}
Sample app which demonstrates the use of the service and task. It will periodically load images in the background from byte arrays and display them in an ImageView, to create a slideshow.
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();
}
}
Utility class to create some test data from image resources.
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;
}
}
The images used here are provided in this answer:
The images should be placed in a resource directory matching the package you place the MonsterImageCreator in.
Some additional points
I have even increased the memory
You can't really do that. The Direct3D textures are native resources of the 3D graphic system used by your JavaFX implementation, you won't have any control over how the memory is used. The memory used can be texture memory in the graphic card video ram, not memory associated with the Java heap.
What you are doing is blowing up that video memory space in some way. How I don't know.
- One way to do it is to load textures that exceed the max size allowed by the graphic device (typically something 4K x 4K pixels or 8K x 8K pixels).
- Another way to do it is to load lots of textures into memory that exceeds the total texture memory of the graphics card (which will vary by graphics card, but is typically multiple gigabytes of compressed texture memory space).
- You can also cause corruption of the texture memory space by incorrectly threaded code causing race conditions.
- Maybe you are running a busy loop that just loads massive amounts of textures into the system without giving the JavaFX texture manager time to appropriately release obsolete textures.
What could be causing the growing pool
Allocating new graphics textures is a frequent operation in a UI application. It is normal that the texture memory pool would change in size dynamically. Textures are used widely, not just for your images, but also for the rendering engine (both in 2D and 3D mode).
Generally, you don't really have to worry about the management of texture memory resources as the JavaFX implementation will take care of that work for you, in a similar way that you don't need to worry much about memory management in a Java application.
The only things you really need to worry about are the standard things that you have to consider in managing memory resources in Java. Those are things like:
- Ensuring you don't have multiple threads corrupt memory.
- Not creating very large objects which will exceed the available memory size.
- Not holding onto object references you no longer need, so that the garbage collector (or JavaFX's internal manager of texture memory resources) can free those resources as it wishes.