I could use some help debugging a memory (leak?) issue. I've made a simple example below. There's some kind of bug in Javafx related to TextFields. The below code adds 2000 TextFields to a FlowPane within a ScrollPane. According to task manager, Java uses ~420mb at this point.
Pressing the add button adds another 2000 TextFields every time it is pressed. Each time adds maybe 80-200 mb (somehow it's not always the same amount of memory??). The remove button removes the TextFields, but memory is never freed. This is with Java jdk 9 where - as far as I understand things - the GC should free up memory that is no longer in use and return it to the OS. Changing TextFields into Texts solves the issue, takes far less memory and actually returns it to the OS when appropriate, but I would prefer to have TextFields. Does anyone know how to fix/work around this? :-)
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class main extends Application
{
private ScrollPane scroll;
private FlowPane pane;
private Scene scene;
private Stage stage;
#Override
public void start(Stage stage) throws Exception
{
try
{
this.stage=stage;
pane = new FlowPane();
Button b1 = new Button("Add 2000");
Button b = new Button("Remove 2000");
b1.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
addTextFields();
}});
b.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
removeTextFields();
System.gc();
}});
pane.getChildren().add(b);
pane.getChildren().add(b1);
scroll = new ScrollPane();
scroll.setContent(pane);
addTextFields();
scene = new Scene(scroll,800,600);
stage.setScene(scene);
stage.show();
}
catch(Exception e)
{
e.printStackTrace();
}
}
private void addTextFields()
{
for(int i=0; i < 2000; i++)
{
//Text text = new Text("T " + i);
TextField textField = new TextField("T "+i);
this.pane.getChildren().add(textField);
}
}
private void removeTextFields()
{
for(int i=2001; i>1; i--)
{
// Text f = (Text) this.pane.getChildren().get(i);
TextField f = (TextField) this.pane.getChildren().get(i);
this.pane.getChildren().remove(f);
}
}
public static void main(String[] args)
{
launch(args);
}
}
Apparently, it's perfectly normal for java 9 to e.g. keep taking up over a GB in heap space memory with only a basic stage, scrollpane and an emptied flowpane in play (that used to contain several thousand TextFields). The memory is never released back to the OS unless forced within VisualVM even after calling the GC explicitly from code.
This GC behaviour doesn't make any sense to me, particularly on a poor man's system with only 4 gb of ram that is already mostly in use before I run Java, but I'll work around it by either using Texts or using a TableView.
Related
I am new in this forum and also to programming. Furthermore, my English is not the best, but I hope you can understand what I mean and help me out.
I want to program a GUI and using JavaFX and the Gauges from the medusa- library. What I need to do is changing the maxValue and the minValue of the Gauge while the program is running. I can change the values, but the scale of the Gauge does not rearrange the ticks properly. For example, when I create a Gauge from 0 to 10 and then set the maxValue to 100, the scale shows all numbers as a major tick and the scale becomes unreadable. Because I could not find how to fix this, I have tried to delete the original Gauge and create simply a new one.
Here is what I have tried(I deleted the rest of the class, because it has over 800 lines):
package application;
import eu.hansolo.medusa.Gauge;
import eu.hansolo.medusa.Gauge.SkinType;
import eu.hansolo.medusa.GaugeBuilder;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
public class Controller {
#FXML
StackPane stackPane;
private Gauge gauge;
private Button button;
#FXML
private void initialize() {
gauge = GaugeBuilder.create().skinType(SkinType.QUARTER).barBackgroundColor(Color.LIGHTGREY)
.needleColor(Color.RED).decimals(0).valueVisible(true).valueColor(Color.BLACK).title("Stromstärke")
.unit("[mA]").subTitle("Phase 1").minValue(0).maxValue(10).build();
stackPane.getChildren().add(gauge);
}
public void setMaxValueGauge(StackPane pStackPane, Gauge pGauge, int intMinValue, int pMaxValue) {
pStackPane.getChildren().remove(pGauge);
Gauge newGauge = GaugeBuilder.create().skinType(pGauge.getSkinType()).barBackgroundColor(pGauge.getBarColor())
.needleColor(pGauge.getNeedleColor()).decimals(0).valueVisible(true).valueColor(Color.BLACK)
.title(pGauge.getTitle()).unit(pGauge.getUnit()).subTitle(pGauge.getSubTitle()).minValue(intMinValue)
.maxValue(pMaxValue).build();
pGauge = null;
pGauge = newGauge;
pStackPane.getChildren().add(pGauge);
}
#FXML
public void testButton() {
setMaxValueGauge(stackPane, gauge, 0, 30);
}
}
The method testButton() is only for testing. When I call testButton() the first time, it works well, but when I use it twice or more, it seems that the old Gauge is not replaced. Instead the new one stacks on top of the old one.
Can you please help me. I need either to fix the ticks of the scale, when I set a new maxValue, or to properly replace the old Gauge in the Stackpane.
You appear to be doing too much to set the max-value.
Here is an MCVE that changes the maxValue.
import eu.hansolo.medusa.Gauge;
import eu.hansolo.medusa.Gauge.SkinType;
import eu.hansolo.medusa.GaugeBuilder;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
*
* #author blj0011
*/
public class MedusaGaugeTest extends Application
{
#Override
public void start(Stage primaryStage)
{
Gauge gauge = GaugeBuilder.create().skinType(SkinType.QUARTER).barBackgroundColor(Color.LIGHTGREY)
.needleColor(Color.RED).decimals(0).valueVisible(true).valueColor(Color.BLACK).title("Stromstärke")
.unit("[mA]").subTitle("Phase 1").minValue(0).maxValue(10).build();;
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1), (ActionEvent event) -> {
if (gauge.getValue() <= gauge.getMaxValue()) {
gauge.setValue(gauge.getValue() + 1);
}
}));
timeline.setCycleCount(Timeline.INDEFINITE);
Button btn = new Button();
btn.setText("Start");
btn.setOnAction((ActionEvent event) -> {
timeline.play();
});
Button btn2 = new Button();
btn2.setText("Increase MaxValue");
btn2.setOnAction((ActionEvent event) -> {
gauge.setMaxValue(15);
});
VBox root = new VBox(gauge, new VBox(btn, btn2));
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
I am in the process of teaching myself JavaFX. Coming from the Swing world there are a lot of similarities between the 2. Especially event processing. Part of my process is to try and mimic an existing application as closely as possible. One of the things I am doing is creating a dialog that will allow the user to select a font to use. There is a text field for them to type in the font name and a list where they can scroll and select one. When they start typing the list will automatically scroll to through the list to start matching what the user is typing. I am also trying to populate the text field with the currently matched font name and then highlight the portion that the user has not typed yet so they can continue to type until the correct match is found.
For example if the user types the letter 't' on Windows the first font found is Tahoma. So the text field will be set to Tahoma and the carat will be positioned right after the 'T' and the 'ahoma' will be highlighted. What happens instead is that the field is populated with Tahoma and the carat is positioned at the end and nothing is highlighted. So it is like it is ignoring the 2 lines of code for positioning and highlighting or the event processor is causing my calls to JavaFX libraries to be run out of order.
I think this may be a bug with JavaFX but it could also be my misunderstanding of the event system. Please let me know which one and why.
Here is a complete sample code showing the problem. Just start typing in the text field to try it out.
package test;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class TestTyping extends Application {
ChangeListener<String> textChange;
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
TextField text = new TextField();
root.setTop(text);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
textChange = (observable, oldValue, newValue) -> {
text.textProperty().removeListener(textChange);
for (String family : Font.getFamilies()) {
if (family.equalsIgnoreCase(newValue) || family.toLowerCase().startsWith(newValue.toLowerCase())) {
text.setText(family);
text.positionCaret(newValue.length());
text.selectEnd();
break;
}
}
text.textProperty().addListener(textChange);
};
text.textProperty().addListener(textChange);
}
public static void main(String... args) {
launch(args);
}
}
Wrap caret position and select end into Platform.runLater. The problem is in events order. I don't know correct details about this issue so I will not provide you a detailed answer, only solution.
Platform.runLater(()-> {
text.positionCaret(newValue.length());
text.selectEnd();
});
Here's an alternative approach entirely, which uses a TextFormatter to modify changes to the text. The advantage here is that it doesn't rely on the "timing" of various property changes with respect to event handling, which is not documented and thus could possibly change in later JavaFX versions. It also avoids the slightly ugly "remove the listener and add it back" idiom.
import java.util.function.UnaryOperator;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class TestTyping extends Application {
ChangeListener<String> textChange;
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
TextField text = new TextField();
root.setTop(text);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
UnaryOperator<Change> filter = c -> {
// for delete, move the caret, or change selection, don't modify anything...
if (c.getText().isEmpty()) {
return c ;
}
for (String family : Font.getFamilies()) {
if (family.toLowerCase().startsWith(c.getControlNewText().toLowerCase())) {
c.setText(family.substring(c.getRangeStart(), family.length()));
c.setAnchor(c.getControlNewText().length());
break ;
}
}
return c ;
};
text.setTextFormatter(new TextFormatter<String>(filter));
}
public static void main(String... args) {
launch(args);
}
}
really liking JavaFX but have come across this problem and wondered if it was a bug.
The ScrollBar.setOnMousePressed() doesn't seem to fire when it has been initialised with a handler. The code below demonstrates the problem:-
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Play extends Application {
public static void main(String[] args) {
launch(args);
}
private static int cnt;
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Bug?");
Button btn = new Button("This text will get replaced by the event handlers");
ScrollBar scrollBar = new ScrollBar();
// When pressing and releasing the ScrollBar thumb, we only get decrements
// If you replace the ScrollBar with say a Button, then the code below works as you might expect.
scrollBar.setOnMousePressed( event -> btn.setText("X" + cnt++));
scrollBar.setOnMouseReleased( event -> btn.setText("X" + cnt--));
VBox root = new VBox();
root.getChildren().add(btn);
root.getChildren().add(scrollBar);
primaryStage.setScene(new Scene(root, 350, 250));
primaryStage.show();
}
}
Note, Im running on JDK 1.8.0_66 64 Bit on Microsoft Windows 10.
A simple workaround, as suggested by James_D, is to use EventFilters instead of setOnMousePressed(), as follows:-
So,
scrollBar.addEventFilter(MouseEvent.MOUSE_PRESSED,
event -> btn.setText("X" + cnt++));
instead of
scrollBar.setOnMousePressed( event -> btn.setText("X" + cnt++));
I believe .setOnMousePressed() should work, but doesn't because of a bug in the library. I've raised with oracle and will update the answer once oracle clarifies.
I'm trying to learn JavaFX. To do so I've been attempting to make a text editor that includes multiple line text box support, as well as the possibility of having syntax highlighting down the road.
Currently, the biggest problem I've been facing is that the ScrollPane I've been encapsulating all my FlowPanes in won't resize according to the size of the Pane it's in. I've been researching this problem for about half a week now and simply cannot get the ScrollPane to just fill the window it's in. The code below displays a JavaFX stage that has working keyboard input and the ScrollPane is always the same size no matter what. Thanks to all in advance!
Here's my Main:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Launcher extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(new DynamicTextBox(),500,500));
primaryStage.show();
}
}
TextBox class:
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.geometry.Orientation;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.Pane;
import javafx.scene.text.Text;
public class DynamicTextBox extends Pane {
//currentLinePane is made to handle all the direct user inputs
//multiLinePane, while not really used yet will create a new line when the enter key is struck.
private FlowPane currentLinePane, multiLinePane;
private ScrollPane editorScroller;
public DynamicTextBox() {
super();
currentLinePane = new FlowPane(Orientation.HORIZONTAL);
multiLinePane = new FlowPane(Orientation.VERTICAL);
multiLinePane.getChildren().add(currentLinePane);
editorScroller = new ScrollPane(multiLinePane);
editorScroller.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
editorScroller.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
editorScroller.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
configureInput(event);
}
});
super.getChildren().add(editorScroller);
editorScroller.requestFocus();
}
private void configureInput(KeyEvent event) {
currentLinePane.getChildren().add(new Text(event.getText()));
}
}
You're using
ScrollPane.ScrollBarPolicy.AS_NEEDED
which, according to the docs at Oracle, "Indicates that a scroll bar should be shown when required." Instead, use
ScrollPane.ScrollBarPolicy.ALWAYS
alternatively, recall these are constants. you can get the height of the parent using boundsInParent: https://docs.oracle.com/javafx/2/api/javafx/scene/Node.html#boundsInParentProperty
alternatively, you can use getParent() to get the parent and then get its height using computeMinWidth() https://docs.oracle.com/javafx/2/api/javafx/scene/Node.html#getParent()
i have the following javafx code when executed with -Xmx10m jvm option, it runs to completion after clicking on the button (it adds and removes 250 TextFields 100000 times) on mac osx but it runs out of memory on windows 7.
on both platforms, java 1.7.0 u25 were used.
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class SimpleTextFieldTest extends Application {
private List<TextField> list = new ArrayList<TextField>();
private Label message = new Label();
private void init(Stage primaryStage) {
System.out.println("Start Testing");
for (int i = 0; i < 250; i++) {
TextField textField = new TextField();
textField.setPrefWidth(100);
textField.setText("hello");
list.add(textField);
}
System.out.println("end of initial textBox");
final VBox root = new VBox();
primaryStage.setScene(new Scene(root, 200, 200));
Button button1 = new Button();
button1.setText("Start");
button1.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
try{
for (int i = 0; i < 100000; i++) {
for(TextField text : list){
root.getChildren().add(text);
}
root.getChildren().removeAll(list);
}
}catch(Exception ex){
ex.printStackTrace();
}
System.out.println("end of Test");
}
});
root.getChildren().add(button1);
root.getChildren().add(message);
}
#Override
public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Running out of memory does not always imply a memory leak. It may simply mean you don't have enough memory to do what you're trying to do. 10 megs isn't much at all. It's likely that the in-memory representations of JavaFX nodes differ somewhat between Mac and Windows and perhaps the Windows representation requires a bit more space, or the base consumption of the memory pool is higher to start with on Windows. Bottom line: it's not realistic to expect that exact same memory usage across platforms. Also, the implementation of the -Xmx option itself may even differ.
After glancing at your code, I don't see any leaks, especially since you're not instantiating new TextField instances each time. And yes, it is very, very possible to leak memory in Java. In some ways it's almost more likely because it gives you a false sense of security.