JavaBean属性:jdk 7 vs jdk 8 - WeakOnTheFeet vs StrongInTheArm

时间:2014-02-04 13:15:03

标签: java swing binding javafx-2 javafx-8

long standing issue(有人称之为 - 可以说是 - feature :)是所有fx绑定所安装的所有侦听器的弱点。因此,如果不对链中的每个环节进行强有力的引用,我们就无法构建属性的“链”。

这种链接的特定类型是JavaBeanProperty:其目的是使javabean属性适应fx属性。通常情况下,没有人对适配器感兴趣,因此它的使用会像

那样
private Parent createContentBean() {
    ...
    // local ref only
    Property property = createJavaBeanProperty();
    Bindings.bindBidirectional(label.textProperty(), property, NumberFormat.getInstance());

.. wondering为什么标签没有更新。将属性更改为强引用将按预期工作(让我感到困惑的是谁负责提供假人,但这是另一个问题):

Property property;
private Parent createContentBean() {
    ...
    // instantiate the field
    property = createJavaBeanProperty();
    Bindings.bindBidirectional(label.textProperty(), property, NumberFormat.getInstance());

长介绍,但几乎在那里:jdk8以某种方式改变了实现,以便第一种方法现在正在工作,不再需要保持对JavaBeanProperty的强引用。另一方面,“链接链接”的自定义实现仍然需要强有力的参考。

问题:

  • 是有意改变的行为,如果是,为什么?
  • 它是如何实现的?代码看起来非常相似......我希望在自定义适配器中尝试类似的东西

一个完整的例子:

public class BeanAdapterExample extends Application {

    private Counter counter;

    public BeanAdapterExample() {
        this.counter = new Counter();
    }

    Property property;
    private Parent createContentBean() {
        VBox content = new VBox();
        Label label = new Label();
        // strong ref
        property = createJavaBeanProperty();
        // local property
        Property property = createJavaBeanProperty();
        Bindings.bindBidirectional(label.textProperty(), property, NumberFormat.getInstance());
        Slider slider = new Slider();
        slider.valueProperty().bindBidirectional(property);
        Button button = new Button("increase");
        button.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent paramT) {
                counter.increase();
            }

        });
        content.getChildren().add(label);
        content.getChildren().add(slider);
        content.getChildren().add(button);
        return content;
    }

    protected JavaBeanDoubleProperty createJavaBeanProperty(){
        try {
            return JavaBeanDoublePropertyBuilder.create()
                    .bean(counter).name("count").build();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void start(Stage stage) throws Exception {
        Scene scene = new Scene(createContentBean());
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        Application.launch(args);
    }


    public static class Counter {

        private double count;

        public Counter() {
            this(0);
        }

        public Counter(double count) {
            this.count = count;
        }

        /**
         * Increases the counter by 1.
         */
        public void increase() {
            setCount(getCount()+ 1.);
        }

        /**
         * @return the count
         */
        public double getCount() {
            return count;
        }

        /**
         * @param count the count to set
         */
        public void setCount(double count) {
            double old = getCount();
            this.count = count;
            firePropertyChange("count", old, getCount());
        }

        PropertyChangeSupport support = new PropertyChangeSupport(this);

        public void addPropertyChangeListener(PropertyChangeListener l) {
            support.addPropertyChangeListener(l);
        }

        public void removePropertyChangeListener(PropertyChangeListener l) {
            support.removePropertyChangeListener(l);
        }

        protected void firePropertyChange(String name, Object oldValue,
                Object newValue) {
            support.firePropertyChange(name, oldValue, newValue);
        }

    }

}

BTW:添加了Swing标签,因为调整核心bean将是迁移中的常见任务

2 个答案:

答案 0 :(得分:2)

提醒我issue去年我偶然发现 - 绑定不会创建强引用,因此如果属性是方法本地字段,属性将被垃圾收集。

答案 1 :(得分:1)

Gropingly试图回答我自己的部分回答:

  • 暂定猜测:这不是故意的。看起来现在JavaBeanProperty永远不会被垃圾收集,这可能不是必需的。
  • 我能找到的唯一区别是在其构造函数中创建的属性的phantomReference(代码片段中的Cleaner):它似乎保持强大到永远不会(?)被释放。如果我在自定义属性中模仿它,它们在链中“工作”,但也不是垃圾收集。不是一种选择,IMO。

属性的jdk8构造函数:

JavaBeanDoubleProperty(PropertyDescriptor descriptor, Object bean) {
    this.descriptor = descriptor;
    this.listener = descriptor.new Listener<Number>(bean, this);
    descriptor.addListener(listener);
    Cleaner.create(this, new Runnable() {
        @Override
        public void run() {
            JavaBeanDoubleProperty.this.descriptor.removeListener(listener);
        }
    });
}

反过来说:如果我将这样的引用添加到任意自定义属性,那么它就像javabeanProperty一样卡在内存中:

protected SimpleDoubleProperty createPhantomedProperty(final boolean phantomed) {
    SimpleDoubleProperty adapter = new SimpleDoubleProperty(){
        {
            // prevents the property from being garbage collected
            // must be done here in the constructor
            // otherwise reclaimed immediately
            if (phantomed) {
                Cleaner.create(this, new Runnable() {
                    @Override
                    public void run() {
                        // empty, could do what here?
                        LOG.info("runnable in cleaner");
                    }
                });
            }
        }

    };

    return adapter;
}

要重现非集合,请将下面的代码片段添加到问题中的示例代码中,在jdk7 / 8中运行并使用您喜欢的工具(使用VisualVM)进行监视:在运行时,单击“创建”以创建100k自由飞行的JavaBeanProperties。在jdk7中,它们甚至从未出现在内存采样器中。在jdk8中,它们被创建(懒散!所以你可以减少数量)并建立起来。强制垃圾收集无效,即使在将它们绑定的底层bean归零后也是如此。

Button create100K = new Button("create 100k properties");
create100K.setOnAction(new EventHandler<ActionEvent>() {

    @Override
    public void handle(ActionEvent paramT) {
        Property propertyFX;
        /// can't measure any effect
        for (int i = 0; i < 100000; i++) {
            propertyFX = createCountProperty();
        }
        LOG.info("created 100k adapters");
    }

});
Button releaseCounter = new Button("release counter");
releaseCounter.setOnAction(new EventHandler<ActionEvent>() {

    @Override
    public void handle(ActionEvent paramT) {
        counter = null;
    }

});

仅供参考:创建一个issue for the potential memory leak - 已标记为已修复,速度很快!不幸的是,修复版本是8u20,不知道该怎么办。我想到的唯一事情是c&amp; p所有JavaBeanXXProperty / Builders并添加修复。以安全限制环境中的严重警告和不可用为代价。此外,我们回到了jdk7的行为(本来是太幸运了,吃蛋糕还有它: - )