我正在将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
中有TilePane
和SplitPane
。每当我单击ListView
中的项目(文件夹名称)时,都必须通过清除先前的子项将图像(10至20张图像)加载到TilePane中。但是它并没有清除内存,并且还在不断增加。
我非常相信这里没有问题。
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();
}
}