Skip to content

Commit 337c781

Browse files
Andy Goryachevkevinrushforth
authored andcommitted
8293444: Creating ScrollPane with same content component causes memory leak
Reviewed-by: kcr, arapte
1 parent cc00c8d commit 337c781

File tree

2 files changed

+75
-9
lines changed

2 files changed

+75
-9
lines changed

modules/javafx.controls/src/main/java/javafx/scene/control/skin/ScrollPaneSkin.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,12 @@
3636
import javafx.animation.Timeline;
3737
import javafx.beans.InvalidationListener;
3838
import javafx.beans.Observable;
39+
import javafx.beans.WeakInvalidationListener;
3940
import javafx.beans.property.DoubleProperty;
4041
import javafx.beans.property.DoublePropertyBase;
4142
import javafx.beans.value.ChangeListener;
4243
import javafx.beans.value.ObservableValue;
44+
import javafx.beans.value.WeakChangeListener;
4345
import javafx.event.EventDispatcher;
4446
import javafx.event.EventHandler;
4547
import javafx.geometry.BoundingBox;
@@ -185,6 +187,7 @@ public class ScrollPaneSkin extends SkinBase<ScrollPane> {
185187
}
186188
};
187189

190+
private final WeakInvalidationListener weakNodeListener = new WeakInvalidationListener(nodeListener);
188191

189192
/*
190193
** The content of the ScrollPane has just changed bounds, check scrollBar positions.
@@ -242,7 +245,7 @@ else if (newValueX > 1.0) {
242245
}
243246
};
244247

245-
248+
private final WeakChangeListener<Bounds> weakBoundsChangeListener = new WeakChangeListener(boundsChangeListener);
246249

247250
/* *************************************************************************
248251
* *
@@ -274,17 +277,17 @@ public ScrollPaneSkin(final ScrollPane control) {
274277
registerChangeListener(control.contentProperty(), e -> {
275278
if (scrollNode != getSkinnable().getContent()) {
276279
if (scrollNode != null) {
277-
scrollNode.layoutBoundsProperty().removeListener(nodeListener);
278-
scrollNode.layoutBoundsProperty().removeListener(boundsChangeListener);
280+
scrollNode.layoutBoundsProperty().removeListener(weakNodeListener);
281+
scrollNode.layoutBoundsProperty().removeListener(weakBoundsChangeListener);
279282
viewContent.getChildren().remove(scrollNode);
280283
}
281284
scrollNode = getSkinnable().getContent();
282285
if (scrollNode != null) {
283286
nodeWidth = snapSizeX(scrollNode.getLayoutBounds().getWidth());
284287
nodeHeight = snapSizeY(scrollNode.getLayoutBounds().getHeight());
285288
viewContent.getChildren().setAll(scrollNode);
286-
scrollNode.layoutBoundsProperty().addListener(nodeListener);
287-
scrollNode.layoutBoundsProperty().addListener(boundsChangeListener);
289+
scrollNode.layoutBoundsProperty().addListener(weakNodeListener);
290+
scrollNode.layoutBoundsProperty().addListener(weakBoundsChangeListener);
288291
}
289292
}
290293
getSkinnable().requestLayout();
@@ -635,8 +638,8 @@ private void initialize() {
635638
ParentHelper.setTraversalEngine(getSkinnable(), traversalEngine);
636639

637640
if (scrollNode != null) {
638-
scrollNode.layoutBoundsProperty().addListener(nodeListener);
639-
scrollNode.layoutBoundsProperty().addListener(boundsChangeListener);
641+
scrollNode.layoutBoundsProperty().addListener(weakNodeListener);
642+
scrollNode.layoutBoundsProperty().addListener(weakBoundsChangeListener);
640643
}
641644

642645
viewRect = new StackPane() {

modules/javafx.controls/src/test/java/test/javafx/scene/control/skin/ScrollPaneSkinTest.java

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -28,6 +28,9 @@
2828
import static org.junit.Assert.assertEquals;
2929
import static org.junit.Assert.assertTrue;
3030

31+
import java.lang.ref.WeakReference;
32+
import java.util.ArrayList;
33+
3134
import javafx.beans.value.ObservableValue;
3235
import javafx.event.Event;
3336
import javafx.event.EventType;
@@ -40,7 +43,11 @@
4043
import javafx.scene.control.ScrollPane;
4144
import javafx.scene.control.skin.ScrollPaneSkin;
4245
import javafx.scene.control.skin.ScrollPaneSkinShim;
43-
import javafx.scene.input.*;
46+
import javafx.scene.input.MouseButton;
47+
import javafx.scene.input.MouseEvent;
48+
import javafx.scene.input.ScrollEvent;
49+
import javafx.scene.input.SwipeEvent;
50+
import javafx.scene.layout.BorderPane;
4451
import javafx.scene.layout.HBox;
4552
import javafx.scene.layout.Pane;
4653
import javafx.scene.layout.StackPane;
@@ -52,6 +59,10 @@
5259
import org.junit.Ignore;
5360
import org.junit.Test;
5461

62+
import com.sun.javafx.tk.Toolkit;
63+
64+
import test.util.memory.JMemoryBuddy;
65+
5566

5667
public class ScrollPaneSkinTest {
5768
private ScrollPane scrollPane;
@@ -860,4 +871,56 @@ public MouseEvent generateMouseEvent(EventType<MouseEvent> type,
860871
return event;
861872
}
862873
}
874+
875+
/**
876+
* Test that ScrollPane object is not leaked when 'same' node
877+
* is used as content for different ScrollPane objects, see JDK-8293444.
878+
*/
879+
@Test
880+
public void testScrollPaneObjLeakWhenUsedSameContent() {
881+
882+
ArrayList<WeakReference<ScrollPane>> refs = new ArrayList<>();
883+
884+
BorderPane bp = new BorderPane();
885+
886+
Stage stage = new Stage();
887+
stage.setScene(new Scene(bp));
888+
stage.show();
889+
890+
Label content = new Label("content");
891+
892+
for (int i = 0; i < 10; i++) {
893+
ScrollPane sp = new ScrollPane(content);
894+
refs.add(new WeakReference<ScrollPane>(sp));
895+
bp.setCenter(sp);
896+
897+
Toolkit.getToolkit().firePulse();
898+
}
899+
900+
bp.setCenter(null);
901+
Toolkit.getToolkit().firePulse();
902+
903+
int ct = 0;
904+
for (WeakReference<ScrollPane> ref : refs) {
905+
JMemoryBuddy.checkCollectable(ref);
906+
if (ref.get() != null) {
907+
ct++;
908+
}
909+
}
910+
911+
// one instance is still held by the 'content' label
912+
assertEquals("One instance should be held by the 'content' label", 1, ct);
913+
914+
// releasing the last instance
915+
content = null;
916+
917+
ct = 0;
918+
for (WeakReference<ScrollPane> ref : refs) {
919+
JMemoryBuddy.checkCollectable(ref);
920+
if (ref.get() != null) {
921+
ct++;
922+
}
923+
}
924+
assertEquals(ct + " references of ScrollPane are not freed.", 0, ct);
925+
}
863926
}

0 commit comments

Comments
 (0)