0

I have a JavaFX program where I continuously set an image in an ImageView using the setImage(new Image(new ByteArrayInputStream(imageBytes))) method within a loop. This entire process is wrapped in Platform.runLater() to ensure UI thread safety.

The issue I'm facing is that when I enable -Dprism.verbose=true, the console continuously prints the message "Growing pool D3D Vram Pool target," followed by an increasing number. Eventually, it leads to a NullPointerException. Surprisingly, adding System.gc() after the setImage call resolves the issue, and the exceptions stop after a short while.

I understand that calling System.gc() in a loop is not recommended for performance reasons, and the garbage collector should handle this automatically. However, without this explicit call, the growing pool and exceptions persist.

I have also tried calling setImage(null) before setting the actual image to inform the garbage collector, but it didn't make any difference. I have even increased the memory, but the issue remains.

Is this behavior normal? What could be causing the growing pool and the NullPointerException? Any suggestions on how to resolve this issue without relying on 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..)

and after a few seconds im getting:

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
                    });
                }
            });
Finn
  • 11
  • 2
  • 3
    Do not busy wait on the JavaFX application thread. Load images in the background and notify observers when ready. – trashgod Jul 06 '23 at 17:01
  • @trashgod how would you do that? The [Image constructors](https://openjfx.io/javadoc/17/javafx.graphics/javafx/scene/image/Image.html#%3Cinit%3E(java.io.InputStream)) that take an InputStream as a parameter, don't have a backgroundLoading option. – jewelsea Jul 06 '23 at 18:04
  • @trashgod can you give me a example code, im using javafx 8 and there is no backgroundloading option – Finn Jul 06 '23 at 20:03
  • 1
    Depending on the use, I'd look at [`Task`](https://stackoverflow.com/search?q=%5Bjavafx%5D+Image+Task) or [`Service`](https://stackoverflow.com/search?q=%5Bjavafx%5D+Image+Service) for `Image`. – trashgod Jul 06 '23 at 21:33

1 Answers1

5

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.
jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • That is why you need to better explain your question when you initially ask it. You will only get relevant and specific answers to a specific, well-explained question (usually with an [mcve]). If you want to improve the question, don't write comments, instead edit the question, or ask a follow up question (of higher quality). What you wrote in your comment, could have been written when you asked the question and example code replicating the issue could have been provided. I will not change or enhance this answer to this question based on additional information. – jewelsea Jul 07 '23 at 11:29