JavaFx On children.clear 内存未释放
JavaFx On children.clear memory not released
我正在将 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
这仅用于演示。在我的实际项目中,我在SplitPane
中有ListView
和TilePane
。每当我单击 ListView
中的项目(文件夹名称)时,我都必须通过清除以前的子项将图像(10 到 20 张图像)加载到 TilePane。但是没有清内存,一直在增加
我相信这里没有问题。
Windows 任务管理器不是测试 Java 内存消耗的可靠方法。 JVM 的工作方式是它会为堆留出一些内存。此内存分配由 Runtime.getRuntime().getTotalMemory()
报告。如果堆开始变满,它将为堆分配更多内存(最多 Runtime.getRuntime().maxMemory()
),或者 运行 垃圾收集器删除未引用的对象。我相信在当前的实现中,JVM 不会将分配给堆的未使用内存释放回系统。您可以分别使用 JVM 选项 -Xms
和 -Xmx
配置初始总内存和最大内存。
在您的应用程序中,当您加载图像时,它们当然会消耗内存。如果需要,堆大小将增长,直到最大内存分配,这是您在 Windows 任务管理器中看到的报告大小。
当您清除磁贴窗格时,在那之后的某个时刻(下一次渲染场景时),图像将从底层图形系统中物理移除,并且相关资源将被释放。此时,图像仍使用堆内存,但它们现在有资格进行垃圾回收。下一次垃圾收集器 运行s(如果需要更多内存,这将自动发生),该内存将被堆回收(而不是被系统回收);因此您会在 Runtime.getRuntime().freeMemory()
中看到更改,但不会在任务管理器中看到更改。
在您的测试代码中,您在清除平铺窗格的子列表之后调用 System.gc()
,但在渲染脉冲发生之前,因此图像不符合垃圾收集条件;因此,您看不到 freeMemory()
的任何变化。 (sleep()
没有帮助,因为它阻塞了 FX Application 线程,阻止它渲染场景。)因为仍然有足够的空闲内存,即使加载了图像,GC 也不需要运行 并且不会自动调用。
这里有一个更完整的测试,它允许您实时监控堆内存使用情况,并通过按钮调用 GC。我在 Mac OS X(我的主机)和 4GB 堆 space(默认情况下)以及 Windows 10 运行ning 上测试了这个Oracle VirtualBox 中的 VM,具有 1GB 堆 space,图像数量减少到 400(由于内存分配给虚拟 OS)。两者都表现出相同的结果:加载图像时内存会增加。如果您加载新图像,GC 会自动启动并且内存消耗或多或少保持不变。如果你清除图像,内存没有变化,但如果你强制GC,堆内存被释放。
一旦堆作为达到最大内存分配。
所有这些行为都完全符合预期。
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/Whosebug/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() {
// Create 1000 new copies of the image we downloaded
// Note: Never do this in real code, you can reuse
// the same image for multiple views. This is just
// to mimic loading 1000 different images into memory.
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 -> {
// Create images views from each of the copies of the image,
// and add them to the image pane:
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();
});
// Labels to display memory usage:
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);
// use an AnimationTimer to update the memory usage on each
// rendering pulse:
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();
}
}
我正在将 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
这仅用于演示。在我的实际项目中,我在SplitPane
中有ListView
和TilePane
。每当我单击 ListView
中的项目(文件夹名称)时,我都必须通过清除以前的子项将图像(10 到 20 张图像)加载到 TilePane。但是没有清内存,一直在增加
我相信这里没有问题。
Windows 任务管理器不是测试 Java 内存消耗的可靠方法。 JVM 的工作方式是它会为堆留出一些内存。此内存分配由 Runtime.getRuntime().getTotalMemory()
报告。如果堆开始变满,它将为堆分配更多内存(最多 Runtime.getRuntime().maxMemory()
),或者 运行 垃圾收集器删除未引用的对象。我相信在当前的实现中,JVM 不会将分配给堆的未使用内存释放回系统。您可以分别使用 JVM 选项 -Xms
和 -Xmx
配置初始总内存和最大内存。
在您的应用程序中,当您加载图像时,它们当然会消耗内存。如果需要,堆大小将增长,直到最大内存分配,这是您在 Windows 任务管理器中看到的报告大小。
当您清除磁贴窗格时,在那之后的某个时刻(下一次渲染场景时),图像将从底层图形系统中物理移除,并且相关资源将被释放。此时,图像仍使用堆内存,但它们现在有资格进行垃圾回收。下一次垃圾收集器 运行s(如果需要更多内存,这将自动发生),该内存将被堆回收(而不是被系统回收);因此您会在 Runtime.getRuntime().freeMemory()
中看到更改,但不会在任务管理器中看到更改。
在您的测试代码中,您在清除平铺窗格的子列表之后调用 System.gc()
,但在渲染脉冲发生之前,因此图像不符合垃圾收集条件;因此,您看不到 freeMemory()
的任何变化。 (sleep()
没有帮助,因为它阻塞了 FX Application 线程,阻止它渲染场景。)因为仍然有足够的空闲内存,即使加载了图像,GC 也不需要运行 并且不会自动调用。
这里有一个更完整的测试,它允许您实时监控堆内存使用情况,并通过按钮调用 GC。我在 Mac OS X(我的主机)和 4GB 堆 space(默认情况下)以及 Windows 10 运行ning 上测试了这个Oracle VirtualBox 中的 VM,具有 1GB 堆 space,图像数量减少到 400(由于内存分配给虚拟 OS)。两者都表现出相同的结果:加载图像时内存会增加。如果您加载新图像,GC 会自动启动并且内存消耗或多或少保持不变。如果你清除图像,内存没有变化,但如果你强制GC,堆内存被释放。
一旦堆作为达到最大内存分配。
所有这些行为都完全符合预期。
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/Whosebug/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() {
// Create 1000 new copies of the image we downloaded
// Note: Never do this in real code, you can reuse
// the same image for multiple views. This is just
// to mimic loading 1000 different images into memory.
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 -> {
// Create images views from each of the copies of the image,
// and add them to the image pane:
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();
});
// Labels to display memory usage:
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);
// use an AnimationTimer to update the memory usage on each
// rendering pulse:
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();
}
}