Skip to content

[BUG] Memory Leak when appending single character to StyleClassedTextArea #781

@asafl1976

Description

@asafl1976

Hi,
I have a strange problem..
When printing a single character to StyleClassedTextArea every 50ms, memory usage keeps growing
until application is very slow and not responding. In the examples below, I'm using a thread to update the textarea but in my real application the character comes from serial port at the same rate (50ms).
This bug does not happen if using richtextfx-fat-0.8.2.jar, only when using richtextfx-fat-0.9.0.jar and up.

Whats strange about it is that when printing more than just a single character at one time, for example - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" or when printing single character with new-line "a\n" than there is no memory leak and everything seems to be OK.

See the code "Memory leak" for the example where there is a memory leak.
See the commented code "NO Memory leak 1" and "NO Memory leak 2" for examples where there is no memory leak.

Any help/suggestions how to solve this will be highly appreciated :)
THANKS

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ObservableBooleanValue;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
import org.fxmisc.flowless.VirtualizedScrollPane;
import org.fxmisc.richtext.StyleClassedTextArea;
import org.fxmisc.undo.UndoManager;
import org.reactfx.value.Val;

import java.util.concurrent.atomic.AtomicBoolean;

public class Main extends Application {
    private Thread memoryMonitorThread;
    private AtomicBoolean runMemoryMonitorThread = new AtomicBoolean(true);
    private AtomicBoolean runCharPrintThread = new AtomicBoolean(true);

    @Override
    public void start(Stage primaryStage) throws Exception{
        AnchorPane root = new AnchorPane();
        StyleClassedTextArea textArea = new StyleClassedTextArea();
        VirtualizedScrollPane scrollPane = new VirtualizedScrollPane<>(textArea);
        textArea.setUndoManager(getNoOpUndoManager());

        AnchorPane.setBottomAnchor(scrollPane, 0.0);
        AnchorPane.setTopAnchor(scrollPane, 0.0);
        AnchorPane.setLeftAnchor(scrollPane, 0.0);
        AnchorPane.setRightAnchor(scrollPane, 0.0);
        root.getChildren().add(scrollPane);

        primaryStage.setTitle("Memory Leak");
        primaryStage.setScene(new Scene(root, 800, 400));
        primaryStage.show();

        new Thread(()-> {
            while(runCharPrintThread.get()) {
                sleep(50);
                Platform.runLater(() -> {
                    textArea.appendText("a");
                });
                sleep(50);
                Platform.runLater(() -> {
                    textArea.deleteText(textArea.getLength() - 1, textArea.getLength());
                });
            }
        }).start();

//        ////////////////////// NO Memory leak 2 /////////////////////////
//        new Thread(()-> {
//            while(runCharPrintThread.get()) {
//                try {
//                    Thread.sleep(50);
//                } catch (InterruptedException e) {
//                }
//                Platform.runLater(() -> {
//                    textArea.appendText("a\n");
//                    scrollPane.scrollYBy(Double.MAX_VALUE);
//                });
//            }
//        }).start();
//        ////////////////////////////////////////////////////////////

        startMemoryUsageMonitorThread();
        primaryStage.setOnCloseRequest(t -> {
            runMemoryMonitorThread.set(false);
            memoryMonitorThread.interrupt();
            runCharPrintThread.set(false);
        });
    }
    private void sleep(long mills) {
        try {
            Thread.sleep(mills);
        } catch (InterruptedException e) {
        }
    }

    private void startMemoryUsageMonitorThread() {
        memoryMonitorThread = new Thread(()-> {
            while(runMemoryMonitorThread.get()) {
                long memory = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024);
                System.out.println("Used Memory: " + memory + "MB");
                try {  Thread.sleep(10000);
                } catch (InterruptedException e) {
                }
            }
        });
        memoryMonitorThread.start();
    }

    private UndoManager getNoOpUndoManager() {
        return new UndoManager() {
            private final Val<Boolean> alwaysFalse = Val.constant(false);
            @Override public boolean undo() { return false; }
            @Override public boolean redo() { return false; }
            @Override public Val<Boolean> undoAvailableProperty() { return alwaysFalse; }
            @Override public boolean isUndoAvailable() { return false; }
            @Override public Val<Boolean> redoAvailableProperty() { return alwaysFalse; }
            @Override public boolean isRedoAvailable() { return false; }
            @Override public boolean isPerformingAction() { return false; }
            @Override public boolean isAtMarkedPosition() { return false; }
            // not sure whether these may throw NPEs at some point
            @Override public Val nextUndoProperty() { return null; }
            @Override public Val nextRedoProperty() { return null; }
            @Override public ObservableBooleanValue performingActionProperty() { return null; }
            @Override public UndoPosition getCurrentPosition() { return null; }
            @Override public ObservableBooleanValue atMarkedPositionProperty() { return null; }
            // ignore these
            @Override public void preventMerge() { }
            @Override public void forgetHistory() { }
            @Override public void close() { }
        };
    }


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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions