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.
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);
}
}
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.
We have been having perpetual performance issues when running JavaFX inside a JFXPanel in Swing based applications.
This seems to only be a problem when running on JDK1.7, because whenever it is possible to run JDK1.8 this works perfectly without changing any code.
The symptoms are that the application seems to render fonts in a fuzzy way and also the performance is terrible (multiple seconds to respond to keypress when typing in a TextField).
We are observing the correct rules about EDT, AWT and Platform threads, so I doubt that this can be the issue.
We are stuck having to support JDK1.7 because this is a plug-in for NetBeans which some users will be running on JDK1.7 for various good reasons and we cannot force them to upgrade.
EDIT: Here is a MCVE which recreates the problem
package javaapplication3;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Test {
private static void initAndShowGUI() {
// This method is invoked on the EDT thread
JFrame frame = new JFrame("Swing and JavaFX");
final JFXPanel fxPanel = new JFXPanel();
frame.add(fxPanel);
frame.setSize(300, 200);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Platform.runLater(new Runnable() {
#Override
public void run() {
initFX(fxPanel);
}
});
}
private static void initFX(JFXPanel fxPanel) {
// This method is invoked on the JavaFX thread
Scene scene = createScene();
fxPanel.setScene(scene);
}
private static Scene createScene() {
Group root = new Group();
Scene scene = new Scene(root, Color.ALICEBLUE);
TextField text = new TextField();
Label label = new Label();
VBox box = new VBox();
label.setText("This is a test label");
box.getChildren().add(label);
box.getChildren().add(text);
root.getChildren().add(box);
return (scene);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
initAndShowGUI();
}
});
}
}
The tests we are performing have very simple javafx with e.g. only an AnchorPane with a TextField on it and absolutely no code behind it, and just typing in the TextField is painfully slow.
Behavior looks very much like lock contention between the Swing and JavaFX threads, but it does not seem like we can find any explanation or solution.
This is not the answer that you are looking for, but since we have the same problem with Java 7 support, the answer is that Java 7 has reached its end of life:
July 2015: Updates for Java 7 are no longer available to the public.
Oracle offers updates to Java 7 only for customers who have purchased
Java support or have Oracle products that require Java 7.
https://www.java.com/en/download/faq/java_7.xml
There are no "good reasons" if problem solving is as easy as using a different java version. You don't break things by upgrading to Java 8.
All I wanted is to update a label as my program is running. I am reading some files and I wanted it to display the name of the file is was reading.
However, it only displays the last file using the code below (basically GUI doesn't respond until the whole process is completed):
static Text m_status_update = new Text(); //I declared this outside the function so dont worry
m_status_update.setText("Currently reading " + file.getName());
I got around 4-5 files and I just want to display the name.
I saw a similar question Displaying changing values in JavaFx Label
, the best answer recommended the following:
Label myLabel = new Label("Start"); //I declared this outside the function so dont worry
myLabel.textProperty().bind(valueProperty);
However the valueProperty is a StringProperty and I am stuck converting a string into a string property.
Also, I saw this Refresh label in JAVAFX , but the OP could update the label based on action. I really dont have any action going on?
If you run the entire process on the FX Application thread, then that is (effectively) the same thread that is being used to display the UI. If both the display of the UI, and your file iteration process are running in the same thread, only one can happen at once. So you prevent the UI from updating until the process is complete.
Here's a simple example where I just pause for 250 milliseconds between each iteration (simulating reading a reasonably large file). One button launches this in the FX Application thread (notice how the UI is unresponsive while this runs - you can't type in the text field). The other button uses a Task to run it in the background, properly scheduling updates to the UI on the FX Application thread.
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class UpdateTaskDemo extends Application {
#Override
public void start(Stage primaryStage) {
Label label = new Label();
Button runOnFXThreadButton = new Button("Update on FX Thread");
Button runInTaskButton = new Button("Update in background Task");
HBox buttons = new HBox(10, runOnFXThreadButton, runInTaskButton);
buttons.setPadding(new Insets(10));
VBox root = new VBox(10, label, buttons, new TextField());
root.setPadding(new Insets(10));
runOnFXThreadButton.setOnAction(event -> {
for (int i=1; i<=10; i++) {
label.setText("Count: "+i);
try {
Thread.sleep(250);
} catch (InterruptedException exc) {
throw new Error("Unexpected interruption");
}
}
});
runInTaskButton.setOnAction(event -> {
Task<Void> task = new Task<Void>() {
#Override
public Void call() throws Exception {
for (int i=1; i<=10; i++) {
updateMessage("Count: "+i);
Thread.sleep(250);
}
return null ;
}
};
task.messageProperty().addListener((obs, oldMessage, newMessage) -> label.setText(newMessage));
new Thread(task).start();
});
primaryStage.setScene(new Scene(root, 400, 225));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
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.