我有一个自定义对话框,添加到场景中,然后再次删除。使用VisualVM进行性能分析时,我注意到即使在GC运行之后,仍然会保留此对话框的实例。
我知道这意味着必须在某个地方引用该对象,所以我查看了引用:
如图所示,this$
有很多引用,这意味着内部类,在这种情况下,它们是绑定或ChangeListener
。更改侦听器可以替换为WeakChangeListener
。我不太确定我应该如何处理Bindings。
此外,有些参考文献乍一看没有多大意义:
bean
或SimpleStringProperty
的SimpleObjectProperty
类型oldParent
value
和Node$1
以下是具体问题:
如何绕过这些强引用,所以对象实际上可以被垃圾收集?在这方面,使用lambda表达式而不是匿名内部类会有什么影响吗?如何通过bean
,oldParent
和value
确定对象的引用位置。
EDIT1:
类型bean
的{{1}}引用在超类中使用,因此我不应该在这里引起问题。一个SimpleStringProperty
SimpleObjectProperty
引用来自提供EventHandler的实用程序方法。我如何解决这个问题,bean
与EventHandler
s类似吗?
EDIT2: 我试图想出一个简单的应用程序来重现同样的事情。我可以管理它,看到我在堆转储中列出了基本相同的字段,但后来发现我保留了对应用程序中场景中删除的组件的引用。一旦我放弃了那个参考,它就被清理干净了。唯一明显的区别是在我的小例子中,Object数组中没有引用。
EDIT3:
我做了一些挖掘,发现代码中有两个地方,当注释掉或不使用时,不会导致该对象符合垃圾回收的条件。第一个是ChangeListener
:
ChangeListener
第二个更复杂一点。该组件是一个具有关闭按钮的对话框。按下该对话框关闭。这是按钮的代码,我不认为这部分是问题所在,但为了完整起见:
sailorState.numberOfSailorsProperty().addListener(new ChangeListener<Number>() {
@Override
public void changed(ObservableValue<? extends Number> observableValue,
Number oldValue, Number newValue) {
int inTavern = newValue.intValue()-sailorsAdditionalOnShip.get();
if (inTavern < 0) {
sailorsAdditionalOnShip.set(Math.max(sailorsAdditionalOnShip.get() + inTavern, 0));
inTavern = 0;
}
sailorsInTavern.set(inTavern);
}
});
以下是实例化按钮的代码片段:
public class OpenPatricianButton extends Control {
protected final StringProperty text;
protected final ReadOnlyObjectProperty<Font> currentFont;
protected final ObjectProperty<EventHandler<MouseEvent>> onAction;
public OpenPatricianButton(String text,
final Font font) {
super();
this.text = new SimpleStringProperty(this, "text", text);
this.currentFont = new ReadOnlyObjectPropertyBase<Font>() {
@Override
public Object getBean() {
return this;
}
@Override
public String getName() {
return "currentFont";
}
@Override
public Font get() {
return font;
}
};
this.onAction = new SimpleObjectProperty<EventHandler<MouseEvent>>(this, "onAction");
this.getStyleClass().add(this.getClass().getSimpleName());
}
@Override
public String getUserAgentStylesheet() {
URL cssURL = getClass().getResource("/ch/sahits/game/javafx/control/"+getClass().getSimpleName()+".css");
return cssURL.toExternalForm();
}
public StringProperty textProperty() {
return text;
}
public String getText() {
return text.get();
}
public void setText(String text) {
this.text.set(text);
}
public Font getFont() {
return currentFont.get();
}
public ObjectProperty<EventHandler<MouseEvent>> onActionProperty() {
return onAction;
}
public EventHandler<MouseEvent> getOnAction() {
return onAction.get();
}
public void setOnAction(EventHandler<MouseEvent> onAction) {
this.onAction.set(onAction);
}
}
public class OpenPatricianSmallWaxButton extends OpenPatricianButton {
public OpenPatricianSmallWaxButton(String text,
final Font font) {
super(text, font);
}
@Override
protected Skin<?> createDefaultSkin() {
return new OpenPatricianSmallWaxButtonSkin(this);
}
public OpenPatricianSmallWaxButton(String text) {
this(text, Font.getDefault());
}
}
public class OpenPatricianSmallWaxButtonSkin extends SkinBase<OpenPatricianSmallWaxButton> {
public OpenPatricianSmallWaxButtonSkin(final OpenPatricianSmallWaxButton button) {
super(button);
InputStream is = getClass().getResourceAsStream("sealingWaxFlattend.png");
Image img = new Image(is);
final ImageView imageView = new ImageView(img);
final Label label = new Label();
label.textProperty().bind(button.textProperty());
label.getStyleClass().add("OpenPatricianSmallWaxButtonLabeled");
label.setFont(button.getFont());
label.onMouseClickedProperty().bind(button.onActionProperty());
label.textProperty().bind(button.textProperty());
imageView.onMouseReleasedProperty().bind(button.onActionProperty());
StackPane stack = new StackPane();
stack.getChildren().addAll(imageView, label);
Group group = new Group(stack);
group.setManaged(false);
button.setPrefHeight(img.getHeight());
button.setPrefWidth(img.getWidth());
getChildren().add(group);
}
}
删除按钮的调用是通过Guava closeButton = new OpenPatricianSmallWaxButton("X", font);
closeButton.setLayoutX(WIDTH - CLOSE_BUTTON_WIDTH - CLOSE_BUTTON_PADDING);
closeButton.setLayoutY(CLOSE_BTN_Y_POS);
closeButton.setOnAction(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
executeOnCloseButtonClicked();
}
});
closeButton.getStyleClass().add("buttonLabel");
getContent().add(closeButton);
完成的。因此代码有点长。它从Application线程开始,然后被发布到事件总线线程,然后最终必须调用AsyncEventBus
:
Platform.runLater
真正奇怪的是,当我从计时器调用protected void executeOnCloseButtonClicked() {
ViewChangeEvent event = new ViewChangeEvent(MainGameView.class, EViewChangeEvent.CLOSE_DIALOG);
clientEventBus.post(event);
}
public void handleViewChange(ViewChangeEvent event) {
if (event.getAddresse().equals(MainGameView.class)) {
if (event.getEventNotice() instanceof DialogTemplate) {
setNewDialog((DialogTemplate) event.getEventNotice());
} else {
sceneEventHandlerFactory.getSceneEventHandler().handleEvent(event.getEventNotice());
}
}
}
public void handleEvent(Object eventNotice) {
Preconditions.checkNotNull(dialogContoller, "Dialog controller must be initialized first");
if (eventNotice == EViewChangeEvent.CLOSE_DIALOG) {
dialogContoller.closeDialog();
}
....
public void closeDialog() {
if (Platform.isFxApplicationThread()) {
closeDialogUnwrapped();
} else {
Platform.runLater(() -> closeDialogUnwrapped());
}
}
private void closeDialogUnwrapped() {
if (dialog != null) {
new Exception("Close dialog").printStackTrace();
getChildren().remove(dialog);
dialog = null;
dialogScope.closeScope();
}
}
时,GC可以清除对话框(前提是ChangeListener
的第一个问题已被注释掉)。换句话说,只有在用鼠标单击关闭对话框时才会发生此行为。