JavaFx On children.clear内存未释放

问题描述 投票:0回答:1

我正在将1000个ImageView列表加载到TilePane。然后,经过一段时间后,我将删除TilePane的所有子级。但是在任务管理器中,我可以看到内存根本没有释放(消耗了6GB RAM)。

这是代码

package application;

import java.io.File;
import java.net.URL;
import java.util.ResourceBundle;

import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.TilePane;

public class Controller implements Initializable {

    @FXML
    private TilePane tilePane;

    @Override
    public void initialize(URL arg0, ResourceBundle arg1) {
        File rootPathFile = new File("C:\\Users\\Flower\\3D Objects\\New folder");
        File[] fileList = rootPathFile.listFiles();

        ObservableList<ImageView> btnList = FXCollections.observableArrayList();

        for (File file : fileList) {
            Image img = new Image(file.toURI().toString());
            ImageView imgView = new ImageView(img);
            imgView.setFitWidth(200);
            imgView.setFitHeight(300);
            btnList.add(imgView);
        }
        tilePane.getChildren().addAll(btnList);

        new Thread(() -> {
            try {
                Thread.sleep(10000);
                System.out.println("\nAfter adding 1000 images:");
                System.out.println("Free Memory: " + Runtime.getRuntime().freeMemory());
                System.out.println("Totel Memory: " + Runtime.getRuntime().totalMemory());

                Thread.sleep(15000);
                Platform.runLater(() -> {
                    try {
                        tilePane.getChildren().clear();

                        Thread.sleep(5000); // to ensure memory changes
                        System.out.println("\nAfter clearing children:");
                        System.out.println("Free Memory: " + Runtime.getRuntime().freeMemory());
                        System.out.println("Totel Memory: " + Runtime.getRuntime().totalMemory());

                        System.gc();
                        Thread.sleep(10000);

                        System.out.println("\nAfter GC() and 10 seconds sleep");
                        System.out.println("Free Memory: " + Runtime.getRuntime().freeMemory());
                        System.out.println("Totel Memory: " + Runtime.getRuntime().totalMemory());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }

}

输出:


After adding 1000 images:
Free Memory: 1044423528
Totel Memory: 4572839936

After clearing children:
Free Memory: 1035263112
Totel Memory: 4572839936

After GC() and 10 seconds sleep
Free Memory: 1045599688
Totel Memory: 4572839936

这仅用于演示。在我的实际项目中,ListView中有TilePaneSplitPane。每当我单击ListView中的项目(文件夹名称)时,都必须通过清除先前的子项将图像(10至20张图像)加载到TilePane中。但是它并没有清除内存,并且还在不断增加。

javafx memory-leaks javafx-8
1个回答
0
投票

我非常相信这里没有问题。

Windows任务管理器不是测试Java内存消耗的可靠方法。 JVM的工作方式是为堆留出一些内存。此内存分配由Runtime.getRuntime().getTotalMemory()报告。如果堆开始变满,它将为堆分配更多内存(最大为Runtime.getRuntime().maxMemory()),或者运行垃圾回收器删除未引用的对象。我相信在当前的实现中,JVM不会将分配给堆的未使用的内存释放回系统。您可以分别使用JVM选项-Xms-Xmx配置初始总内存和最大内存。

在您的应用程序中,当您加载图像时,它们当然会消耗内存。如果需要,堆大小将增加,直到最大内存分配,这就是Windows任务管理器报告的大小。

当您清除磁贴窗格时,此后不久的某个时刻(下一次渲染场景时),图像将从物理上从底层图形系统中删除,并且相关资源将被释放。此时,图像仍然使用堆内存,但是它们现在可以进行垃圾回收了。下次垃圾回收器运行时(如果需要更多内存,它将自动发生),该内存将被堆(而不是系统)回收;因此您会在Runtime.getRuntime().freeMemory()中看到更改,但在任务管理器中看不到。

[在您的测试代码中,您在清除图块窗格的子级列表之后但在发生渲染脉冲之前调用了System.gc(),因此图像不符合垃圾回收的条件;因此,您看不到freeMemory()的任何更改。 (sleep()无济于事,因为它阻塞了FX Application线程,阻止了它渲染场景。)由于仍然有足够的可用内存,即使加载了图像,GC也不需要运行并不会自动调用。

这是一个更完整的测试,它使您可以实时监视堆内存使用情况,并通过按钮调用GC。我在具有4GB堆空间(默认情况下)的Mac OS X(我的主机)上以及在Oracle VirtualBox中的VM上运行的Windows 10(具有1GB堆空间并将图像数量减少到400(由于分配给虚拟OS的内存)。两者都显示出相同的结果:加载图像时内存增加。如果加载新图像,GC将自动启动,并且内存消耗或多或少保持恒定。如果清除图像,则内存没有任何变化,但是如果您强行使用GC,则会释放堆内存。

一旦堆达到最大内存分配,OS工具(Mac上的活动监视器,Windows上的任务管理器就会报告或多或少的恒定内存使用情况。

所有这些行为完全符合预期。

import java.util.ArrayList;
import java.util.List;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelReader;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.TilePane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

/**
 * JavaFX App
 */
public class App extends Application {

    private static final String IMAGE_URL = "https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-logo.png";

    private Image image ;

    @Override
    public void init() {
        // download image:
        image = new Image(IMAGE_URL, false);
    };


    @Override
    public void start(Stage primaryStage)  {
        TilePane imagePane = new TilePane();

        Button load = new Button("Load Images");


        Button clear = new Button("Clear");
        clear.setOnAction(e -> imagePane.getChildren().clear());

        Button gc = new Button("GC");
        gc.setOnAction(e -> System.gc());

        HBox buttons = new HBox(load, clear, gc);
        buttons.setSpacing(5);

        load.setOnAction(e -> {
            imagePane.getChildren().clear();
            ProgressIndicator progressIndicator = new ProgressIndicator(ProgressIndicator.INDETERMINATE_PROGRESS);
            TilePane.setAlignment(progressIndicator, Pos.CENTER);
            imagePane.getChildren().add(progressIndicator);
            buttons.setDisable(true);

            PixelReader reader = image.getPixelReader();
            int w = (int)image.getWidth();
            int h = (int)image.getHeight();

            Task<List<Image>> createImagesTask = new Task<>() {
                @Override
                public List<Image> call() {
                    List<Image> images = new ArrayList<>(1000);
                    for (int i = 0 ; i < 1000 ; i++) {
                        images.add( new WritableImage(reader, w, h));
                        updateProgress(i, 1000);
                    }
                    return images ;
                }

            };

            progressIndicator.progressProperty().bind(createImagesTask.progressProperty());

            createImagesTask.setOnSucceeded(evt -> {
                imagePane.getChildren().clear();
                for (Image img : createImagesTask.getValue()) {
                    ImageView imageView = new ImageView(img);
                    imageView.setFitWidth(200);
                    imageView.setPreserveRatio(true);
                    imagePane.getChildren().add(imageView);
                }
                buttons.setDisable(false);
            });

            Thread t = new Thread(createImagesTask);
            t.setDaemon(true);
            t.start();
        });

        Label freeMem = new Label("Free memory:");
        Label usedMem = new Label("Used memory:");
        Label totalMem = new Label("Total memory:");
        Label maxMem = new Label("Max memory:");
        VBox memoryMon = new VBox(freeMem, usedMem, totalMem, maxMem);
        memoryMon.setSpacing(5);

        AnimationTimer memTimer = new AnimationTimer() {

            @Override
            public void handle(long now) {
                long free = Runtime.getRuntime().freeMemory();
                long total = Runtime.getRuntime().totalMemory();
                long max = Runtime.getRuntime().maxMemory();
                freeMem.setText(String.format("Free memory: %,d", free));
                usedMem.setText(String.format("Used memory: %,d", total-free));
                totalMem.setText(String.format("Total memory: %,d", total));
                maxMem.setText(String.format("Max memory: %,d", max));
            }

        };
        memTimer.start();

        BorderPane root = new BorderPane();
        root.setCenter(new ScrollPane(imagePane));
        root.setTop(buttons);
        root.setBottom(memoryMon);

        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.setWidth(800);
        primaryStage.setHeight(500);
        primaryStage.show();
    }



    public static void main(String[] args) {
        launch();
    }

}
© www.soinside.com 2019 - 2024. All rights reserved.