TableCell's add a listener to the visibleLeafColumns property of TableView in the method TableCell.tableViewPropertyImpl(). This listener is not removed until the TableCells tableView property changes (which is probably almost never). We have long running fast moving tables and this leak is catastrophic for our application.
We have hacked via reflection into the guts of TableView to clean up these references ourselves. It ain't pretty but it works. Should help you identify exactly where the issue is:
static {
vlcField = TableView.class.getDeclaredField("visibleLeafColumns");
vlcField.setAccessible(true);
listenerHelperField = ObservableListWrapper.class.getDeclaredField("listenerHelper");
listenerHelperField.setAccessible(true);
weakRefField = WeakListChangeListener.class.getDeclaredField("ref");
weakRefField.setAccessible(true);
}
public static void reap(TableView<?> tableView) {
try {
List<ListChangeListener> toRemove = null;
ObservableListWrapper<?> visibleLeafColumns = (ObservableListWrapper<?>) vlcField.get(tableView);
ListListenerHelper<?> listenerHelper = (ListListenerHelper<?>) listenerHelperField.get(visibleLeafColumns);
if (listenerHelper != null) {
Field changeListenersField = listenerHelper.getClass().getDeclaredField("changeListeners");
changeListenersField.setAccessible(true);
ListChangeListener<?>[] changeListeners = (ListChangeListener<?>[]) changeListenersField.get(listenerHelper);
for (ListChangeListener<?> changeListener : changeListeners) {
if (changeListener instanceof WeakListChangeListener) {
WeakReference weakReference = (WeakReference) weakRefField.get(changeListener);
if (weakReference != null && weakReference.get() == null) {
if (toRemove == null) {
toRemove = new ArrayList<ListChangeListener>();
}
toRemove.add(changeListener);
}
}
}
}
if (toRemove != null) {
System.out.println("Removing " + toRemove.size() + " refs");
for (ListChangeListener listChangeListener : toRemove) {
visibleLeafColumns.removeListener(listChangeListener);
}
}
} catch (Exception ignore) {
}
}
We have hacked via reflection into the guts of TableView to clean up these references ourselves. It ain't pretty but it works. Should help you identify exactly where the issue is:
static {
vlcField = TableView.class.getDeclaredField("visibleLeafColumns");
vlcField.setAccessible(true);
listenerHelperField = ObservableListWrapper.class.getDeclaredField("listenerHelper");
listenerHelperField.setAccessible(true);
weakRefField = WeakListChangeListener.class.getDeclaredField("ref");
weakRefField.setAccessible(true);
}
public static void reap(TableView<?> tableView) {
try {
List<ListChangeListener> toRemove = null;
ObservableListWrapper<?> visibleLeafColumns = (ObservableListWrapper<?>) vlcField.get(tableView);
ListListenerHelper<?> listenerHelper = (ListListenerHelper<?>) listenerHelperField.get(visibleLeafColumns);
if (listenerHelper != null) {
Field changeListenersField = listenerHelper.getClass().getDeclaredField("changeListeners");
changeListenersField.setAccessible(true);
ListChangeListener<?>[] changeListeners = (ListChangeListener<?>[]) changeListenersField.get(listenerHelper);
for (ListChangeListener<?> changeListener : changeListeners) {
if (changeListener instanceof WeakListChangeListener) {
WeakReference weakReference = (WeakReference) weakRefField.get(changeListener);
if (weakReference != null && weakReference.get() == null) {
if (toRemove == null) {
toRemove = new ArrayList<ListChangeListener>();
}
toRemove.add(changeListener);
}
}
}
}
if (toRemove != null) {
System.out.println("Removing " + toRemove.size() + " refs");
for (ListChangeListener listChangeListener : toRemove) {
visibleLeafColumns.removeListener(listChangeListener);
}
}
} catch (Exception ignore) {
}
}