JavaFX分页,在setPageFactory上泄漏?

jav*_*jon 4 java memory-leaks javafx javafx-2 javafx-8

在下面的示例中,如果继续按"下一组"按钮,最终应用程序堆将耗尽.将堆设置为较小的数字,例如-mx50m -ms50m,它将很快达到上限.您可以看到JVisualVM之类的经典楼梯内存消耗.在使用YourKit进行性能分析时,发现有许多来自javafx.*和java.*类包的对象实例没有被CG.setPageFactory似乎是罪魁祸首.也许这段代码做的事情不应该,但到目前为止,我怀疑Windows 8上的JavaFX 2.2分页控制,JDK 1.7_60.Window 8,Java FX 8,JDK 1.8_05也出现了同样的问题.

这是示例或JavaFX Pagination.setPageFactory的问题吗?

public class PaginationSample extends Application {
    private static final String[] PAGE_TEXTS_0 = {"Time wounds all heals.", "The more I see, the less I know for sure.", 
    "Reality leaves a lot to the imagination.", "It's weird not to be weird."};
    private static final String[] PAGE_TEXTS_1 = {"Fermions", "Quarks", "Leptons", "Bosons", "Gluon", "Graviton"};
    private static final String[] PAGE_TEXTS_2 = {"AAAAAAA", "BBBBB", "CCCCCCCC"};
    private static final String[][] ALL_GROUPS = {PAGE_TEXTS_0, PAGE_TEXTS_1, PAGE_TEXTS_2};

    private int groupIndex;

    @Override
    public void start(Stage stage) {
        final Pagination pagination = new Pagination();
        setPagesGroup(pagination);

        Button button = new Button("Next group");
        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent actionEvent) {
                nextGroup();
                setPagesGroup(pagination);
            }
        });

        BorderPane pane = new BorderPane();
        pane.setCenter(pagination);
        pane.setBottom(button);

        stage.setScene(new Scene(pane, 400, 250));
        stage.setTitle("What's leaking?");

        stage.show();
    }

    private void setPagesGroup(Pagination pagination) {
        pagination.setPageFactory(createPageFactory());
        pagination.setPageCount(ALL_GROUPS[groupIndex].length);
    }

    private Callback<Integer, Node> createPageFactory() {
        return new Callback<Integer, Node>() {
            @Override
            public Node call(Integer pageIndex) {
                return createPage(ALL_GROUPS[groupIndex][pageIndex]);
            }

            private Node createPage(String text) {
                return new Label(text);
            }
        };
    }

    private void nextGroup() {
        if (++groupIndex == ALL_GROUPS.length) {
            groupIndex = 0;
        }
    }

    public static void main(String... args) {
        launch(args);
    }
}
Run Code Online (Sandbox Code Playgroud)

下面测试期间剖析器的证据.在测试之前和结束时,强制进行垃圾收集.

YourKit在单击按钮时显示堆正在消耗

补充调查:

如果行

//  pagination.setPageFactory(createPageFactory());
Run Code Online (Sandbox Code Playgroud)

注释掉该示例仍然有效,因为工厂仍在提供页面,而setPageCount方法强制PaginationSkin类调用其resetIndexes方法.setPageFactory和setPageCount都调用PaginationSkin.resetIndexes并发生内存消耗.也许PaginationSkin.java有问题

jav*_*jon 5

请投票支持待解决的问题,您必须登录.

https://javafx-jira.kenai.com/browse/RT-38058

Java 7和8代码调用

    pagination.setPageFactory(...);
    pagination.setPageCount(...);
Run Code Online (Sandbox Code Playgroud)

会引发这种泄漏.


PaginationSkin类似乎有泄漏.验证从中复制源

http://hg.openjdk.java.net/openjfx/2.2.2/master/rt/file/98d1e63be240/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/PaginationSkin.java

然后通过消除和分析内存的过程,你会发现内部类IndicatorButton中的这两个添加侦听器导致泄漏:

@ line 1179

            getSkinnable().getStyleClass().addListener(new ListChangeListener<String>() {
                @Override
                public void onChanged(Change<? extends String> change) {
                    setIndicatorType();
                }
            });


@ line 1197

            tooltipVisibleProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue<? extends Boolean> ov, Boolean oldValue, Boolean newValue) {
                    setTooltipVisible(newValue);
                }
            });
Run Code Online (Sandbox Code Playgroud)

由于内部类后引用,在从节点树中删除IndicatorButton对象后,垃圾收集器无法删除这些对象事件(getChildren.clear()).在IDE中,如果暂时使IndicatorButton静态,则可以中断编译并查看对外部类的突出显示的引用.当这两个听众被注释掉时,泄漏就会消失.如果您将这两个侦听器保留在其中,并在侦听器中注释掉工作代码行,则仍会发生泄漏.

解:

  1. 创建自己的PaginationSkin版本(上面的源链接)并将其应用于您的Pagination控件对象(通过pagination.setSkin或CSS)
  2. 在您的类中,修改IndicatorButton类以具有将释放添加的侦听器的release()方法
  3. 在调用getChildren.clear()以释放indicatorButtons之前,在每个IndicatorButton对象上调用release.