While working with a TabPane I noticed that old tab content that had been replaced would not be GC'ed unless the tab it was removed from was actually viewed.
Run the provided test class:
1. Click the Replace Tab Content button a number of times.
2. Press the Print Report button and observe the console. It will report that the all the StackPane's that have been set as content are still strongly references except for a couple.
3. Turn on the Enable Workaround checkbox.
4. Press the Replace Tab Content button numerous times and then Print Report. You will now see that the old content StackPanes are being collected.
The enable workaround button toggles visibility of the tab content's parent before replacing it with new content which appears to fix the problem.
********************************************************* Test Class **************************************************************
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class TabPaneTest extends Application {
final Set<WeakReference<Object>> refs_ = new HashSet<>();
@Override public void start(final Stage primaryStage) throws Exception {
primaryStage.centerOnScreen();
primaryStage.setHeight(350);
primaryStage.setWidth(500);
final TabPane tabPane = new TabPane();
for ( int i = 0; i < 20; i++ ) {
tabPane.getTabs().add( new Tab("" +i) );
}
final CheckBox checkbox = new CheckBox("Enable Workaround");
checkbox.setSelected(false);
Button replaceButton = new Button("Replace Tab Content");
replaceButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
for( Tab tab: tabPane.getTabs() ) {
Label label = new Label( tab.getText() );
label.setStyle("-fx-font-size: 3em");
StackPane content = new StackPane(label);
if ( tab.getContent() != null && checkbox.isSelected() &&
!tab.isSelected() ) {
tab.getContent().getParent().setVisible(true);
tab.getContent().getParent().setVisible(false);
}
tab.setContent( content );
refs_.add( new WeakReference<Object>(content) );
}
}
});
replaceButton.fire(); // initialize tabs
Button printButton = new Button("Print References");
printButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
Runtime.getRuntime().gc();
Set<WeakReference<Object>> collected = new HashSet<>();
for ( WeakReference<Object> ref: refs_ ) {
if ( ref.get() == null ) {
collected.add(ref);
}
}
refs_.removeAll(collected);
System.out.println("*********************************************");
System.out.println(String.format("%d objects collected.",
collected.size()));
System.out.println(String.format("%d objects remain.",
refs_.size()));
HashMap<String, Integer> remainingMap = new HashMap<>();
for ( WeakReference<Object> ref: refs_ ) {
Object ob = ref.get();
if ( ob != null ) {
String className = ob.getClass().getSimpleName();
Integer count = remainingMap.get(className);
count = count == null ? 1 : count + 1;
remainingMap.put(className, count);
}
}
for ( Entry<String, Integer> entry: remainingMap.entrySet() ) {
System.out.println(
" " + entry.getValue() + " " +entry.getKey() +" remain");
}
System.out.println("*********************************************");
}
});
HBox box = new HBox(10, checkbox, replaceButton, printButton );
box.setMaxWidth(Region.USE_PREF_SIZE);
box.setAlignment(Pos.BASELINE_LEFT);
box.setPadding( new Insets(10));
BorderPane borderPane = new BorderPane(tabPane, null, null, box, null);
BorderPane.setAlignment(box, Pos.CENTER);
primaryStage.setScene( new Scene( borderPane ));
primaryStage.show();
}
public static void main(String[] args) throws Exception {
launch(args);
}
}
Run the provided test class:
1. Click the Replace Tab Content button a number of times.
2. Press the Print Report button and observe the console. It will report that the all the StackPane's that have been set as content are still strongly references except for a couple.
3. Turn on the Enable Workaround checkbox.
4. Press the Replace Tab Content button numerous times and then Print Report. You will now see that the old content StackPanes are being collected.
The enable workaround button toggles visibility of the tab content's parent before replacing it with new content which appears to fix the problem.
********************************************************* Test Class **************************************************************
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class TabPaneTest extends Application {
final Set<WeakReference<Object>> refs_ = new HashSet<>();
@Override public void start(final Stage primaryStage) throws Exception {
primaryStage.centerOnScreen();
primaryStage.setHeight(350);
primaryStage.setWidth(500);
final TabPane tabPane = new TabPane();
for ( int i = 0; i < 20; i++ ) {
tabPane.getTabs().add( new Tab("" +i) );
}
final CheckBox checkbox = new CheckBox("Enable Workaround");
checkbox.setSelected(false);
Button replaceButton = new Button("Replace Tab Content");
replaceButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
for( Tab tab: tabPane.getTabs() ) {
Label label = new Label( tab.getText() );
label.setStyle("-fx-font-size: 3em");
StackPane content = new StackPane(label);
if ( tab.getContent() != null && checkbox.isSelected() &&
!tab.isSelected() ) {
tab.getContent().getParent().setVisible(true);
tab.getContent().getParent().setVisible(false);
}
tab.setContent( content );
refs_.add( new WeakReference<Object>(content) );
}
}
});
replaceButton.fire(); // initialize tabs
Button printButton = new Button("Print References");
printButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
Runtime.getRuntime().gc();
Set<WeakReference<Object>> collected = new HashSet<>();
for ( WeakReference<Object> ref: refs_ ) {
if ( ref.get() == null ) {
collected.add(ref);
}
}
refs_.removeAll(collected);
System.out.println("*********************************************");
System.out.println(String.format("%d objects collected.",
collected.size()));
System.out.println(String.format("%d objects remain.",
refs_.size()));
HashMap<String, Integer> remainingMap = new HashMap<>();
for ( WeakReference<Object> ref: refs_ ) {
Object ob = ref.get();
if ( ob != null ) {
String className = ob.getClass().getSimpleName();
Integer count = remainingMap.get(className);
count = count == null ? 1 : count + 1;
remainingMap.put(className, count);
}
}
for ( Entry<String, Integer> entry: remainingMap.entrySet() ) {
System.out.println(
" " + entry.getValue() + " " +entry.getKey() +" remain");
}
System.out.println("*********************************************");
}
});
HBox box = new HBox(10, checkbox, replaceButton, printButton );
box.setMaxWidth(Region.USE_PREF_SIZE);
box.setAlignment(Pos.BASELINE_LEFT);
box.setPadding( new Insets(10));
BorderPane borderPane = new BorderPane(tabPane, null, null, box, null);
BorderPane.setAlignment(box, Pos.CENTER);
primaryStage.setScene( new Scene( borderPane ));
primaryStage.show();
}
public static void main(String[] args) throws Exception {
launch(args);
}
}