Утечка памяти с таблицей javaFX и sqlite - PullRequest
0 голосов
/ 05 декабря 2018

Я создаю систему выставления счетов в JavaFX и загружаю данные из файла sqlite в табличное представление, используя cellValueFactory с объектом "invoice".Я решил загрузить последние 100 записей для повышения производительности, а затем иметь две кнопки для перемещения между объектами счета-фактуры, вызывая базу данных sqlite и получая следующие 100 вверх или вниз.Каждый раз, когда используются кнопки и отображаются следующие 100 или -100, он использует небольшой объем памяти и удерживается, а не восстанавливается, чем дольше его использование станет проблемой.

Я слышал отдругие потоки, что это может быть вызвано сильными ссылками в слушателях, но я не уверен в этом.

Нет ошибок, только небольшая утечка памяти, которую я не могу исправить.Я не показываю весь свой код, чтобы, надеюсь, не чрезмерно усложнять мой код, но, если это необходимо, пожалуйста, дайте мне знать.

Я учусь в старшей школе и обучаю себя самому Swing, а затем перешел на javaFX 3несколько месяцев назад я был относительно новичком в программировании с использованием графического интерфейса пользователя, поэтому, если есть какие-либо советы по поводу моего стиля кода или каких-либо вредных привычек, пожалуйста, я бы с удовольствием изучил.

основной класс (Launcher)

package main;

import dbUtil.sqlitedb;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.stage.Stage;

import java.util.ArrayList;

//this class launches gui of invoices then distributes the data to the classes.
public class main extends Application {
    /*
    The main class is to access data and process it to and from the sqlitedb object and the invoiceController.
     */


    private boolean saved = true;
    private Parent root;
    private static sqlitedb dataBase;
    private static invoiceDataBaseController invoicesController;
    private Scene scene;
    private static ArrayList<invoiceOBJ> invoiceDataBase;
    private static int indexStart = 100;

    public static void main(String[] args) {

        launch(args);
    }


    public main() {
        invoiceDataBase = new ArrayList<>();



    }


    public void loadInvoices() {
        //dataBase.generateInvoices_test();
        invoiceDataBase = dataBase.listInvoices(indexStart);

    }

    public void setIndexStart(int amount) {
        //the indexStart for the sqlitedb class to use for portioning invoices.
        if (indexStart + amount == 0 || indexStart + amount < 0) {

        } else {
            indexStart += amount;
        }
    }
    // To reset table view's contents with new invoice page.
    public void refreshTable() {
        dataBase.connect();
        invoiceDataBase = dataBase.listInvoices(indexStart);
        invoicesController.addTableItems(invoiceDataBase);
        dataBase.disconnect();


    }



    @Override
    public void start(Stage primaryStage) throws Exception {

        initialize(primaryStage);


    }


    public Scene getScene() {
        return scene;
    }

    public void Loading(Alert alert) {
        // Loading alert before showing main gui
        alert.setHeaderText("Loading Files");
       // System.out.println("loading");
        alert.show();
    }

    public void initialize(Stage stage) {
        try {

            dataBase = new sqlitedb();
            //System.out.println("Loading files");
            //saveInvoicesAndCustomers(generateCustomersAndInvoices());


            FXMLLoader loader = new FXMLLoader(getClass().getResource("main.fxml"));

            Alert alert = new Alert(Alert.AlertType.INFORMATION, "Loading data..");


            Loading(alert);


            loadInvoices();


            root = loader.load();

            invoicesController = loader.getController();
            invoicesController.setStageAndListeners(stage, this, invoiceDataBase);


            scene = new Scene(root);
            scene.getStylesheets().add(getClass().getResource("resources/fxml.css").toExternalForm());
            stage.setTitle("JavaInvoice");
            stage.setScene(scene);


            stage.show();
            alert.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

соответствующие методы в объекте sqliteDB:

Этот метод в sqlitedb используется для вызова начальных счетов-фактур, а затем для дальнейшего вызова в контроллере.

     public ArrayList<invoiceOBJ> listInvoices(int indexStart) {
        //invoiceAmount  = invoices to index.
        ArrayList<invoiceOBJ> invoices = new ArrayList<>();
        Long time = System.currentTimeMillis(); //made to test indexing time
        //System.out.println(indexStart + "index start");
        try {
            //System.out.println("in list Invoices");

            stmt = c.createStatement();
            stmt.setFetchSize(1000);

            ResultSet rs = stmt.executeQuery(String.format("SELECT id,year,vin,carModel,condition,licenseState,regNum,stage,vehicleID,paintCode,dateOfInvoice," +
                    "bodyStyle,manufacturer, customerID  FROM invoices LIMIT 100  OFFSET (SELECT COUNT(*) FROM invoices)-%d", indexStart));
            int id;
            String customerID;
            String year;
            String vin;
            String carModel;
            String condition;
            String licenseState;
            String regNum;
            String stage;
            String vehicleID;
            String paintCode;
            String dateOfInvoice;
            String bodyStyle;
            String manufacturer;
            c.setAutoCommit(false);
            stmt.setFetchSize(1000);

            while (rs.next()) {


                id = rs.getInt(1);
                year = rs.getString(2);
                vin = rs.getString(3);
                carModel = rs.getString(4);
                condition = rs.getString(5);
                licenseState = rs.getString(6);
                regNum = rs.getString(7);
                stage = rs.getString(8);
                vehicleID = rs.getString(9);
                paintCode = rs.getString(10);
                dateOfInvoice = rs.getString(11);
                bodyStyle = rs.getString(12);
                manufacturer = rs.getString(13);
                customerID = rs.getString(14);


//                System.out.println(String.format("ID: %d , CustomerID: %s Year: %s, Vin: %s, \ncarModel %s, condition: %s, licenseState: %s \n" +
//                                "regNum: %s, stage: %s vehicleID: %s, paintCode: %s, dateOfInvoice: %s, bodyStyle:%s", id, customerID, year, vin, carModel
//                        , condition, licenseState, regNum, stage, vehicleID, paintCode, dateOfInvoice, bodyStyle));


                //add to invoice list.

                invoices.add(new invoiceOBJ(id,
                        carModel, manufacturer,
                        vin, condition, licenseState,
                        regNum, stage, vehicleID, paintCode,
                        bodyStyle, year, dateOfInvoice, findCustomer(customerID)));


                // System.out.println("added A customer");


            }
            stmt.close();
            rs.close();
            c.close();
            //System.out.println("load complete..");
        } catch (Exception e) {
            e.printStackTrace();
        }
        // System.out.println(System.currentTimeMillis() - time);

        return invoices;

    }
public HashMap<String, String> findCustomer(String id) {

    String sql = String.format("SELECT firstName, lastName,id, date FROM customers WHERE id=%s", id);
    HashMap<String, String> customerData = new HashMap<>();
    try {

        Statement stmt = c.createStatement();
        ResultSet data = stmt.executeQuery(sql);


        customerData.put("firstName", data.getString("firstName"));
        customerData.put("lastName", data.getString("lastName"));
        customerData.put("date", data.getString("date"));
        customerData.put("id", data.getString("id"));

        stmt.close();
        data.close();

    } catch (Exception e) {
        e.printStackTrace();
    }

    return customerData;
}

invoiceController Объект:

package main;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

public class invoiceDataBaseController implements Initializable {
    main main;
    @FXML
    private Button next = new Button();

    private invoiceLoader customerPage;
    @FXML
    private TableView<invoiceOBJ> invoices = new TableView<>();
    @FXML
    private Button back = new Button();
    @FXML
    private TextField searchField = new TextField();
    @FXML
    private Button confirmButton = new Button();
    @FXML
    private StackPane root = new StackPane();
    @FXML
    private ChoiceBox choice = new ChoiceBox();
    private Stage mainStage;


    public invoiceDataBaseController() {
        customerPage = new invoiceLoader();

    }



    public TextField getSearchField() {
        return searchField;
    }



    //Setup the table of people
    public void setTable() {
        choice.getItems().addAll("ID", "FirstName", "LastName", "Date", "Manufacturer");
        //set specific columns to table.
        TableColumn<invoiceOBJ, String> id = new TableColumn<>("ID:");
        TableColumn<invoiceOBJ, String> firstName = new TableColumn<invoiceOBJ, String>("First Name");
        TableColumn<invoiceOBJ, String> lastName = new TableColumn<invoiceOBJ, String>("Last Name");
        TableColumn<invoiceOBJ, String> dateOfInvoice = new TableColumn<invoiceOBJ, String>("Date");
        TableColumn<invoiceOBJ, String> manufac = new TableColumn<invoiceOBJ, String>("Manufacturer");
        TableColumn<invoiceOBJ, String> model = new TableColumn<invoiceOBJ, String>("Model");
        TableColumn<invoiceOBJ, String> vinNum = new TableColumn<invoiceOBJ, String>("Last 6 VIN");

        invoices.setEditable(true);

        //get data from objects.
        id.setCellValueFactory(p -> p.getValue().getGraphicalData("id"));
        firstName.setCellValueFactory(param -> param.getValue().getCustomer().getFirstName());
        lastName.setCellValueFactory(cellData -> cellData.getValue().getCustomer().getLastName());
        dateOfInvoice.setCellValueFactory(param -> param.getValue().getGraphicalData("dateOfInvoice"));
        manufac.setCellValueFactory(param -> param.getValue().getGraphicalData("manufacturer"));
        model.setCellValueFactory(param -> param.getValue().getGraphicalData("carModel"));
        vinNum.setCellValueFactory(param -> param.getValue().getGraphicalData("vin"));

        //set how dates get sorted
        dateOfInvoice.setComparator((t, t1) -> {
            try {

                SimpleDateFormat format = new SimpleDateFormat("MMM/dd/yyyy");

                return Long.compare(format.parse(t).getTime(), format.parse(t1).getTime());
            } catch (ParseException p) {
                p.printStackTrace();
            }
            return -1;

        });
        id.setComparator(Comparator.comparingInt(Integer::parseInt));
        //end
        //Add values to table view columns and rows.


        //end
        invoices.getColumns().addAll(id, firstName, lastName, dateOfInvoice, manufac, model, vinNum);
        //  dateOfInvoice.setSortType(TableColumn.SortType.DESCENDING);
        id.setSortType(TableColumn.SortType.DESCENDING);

        invoices.getSortOrder().add(id);
        choice.setValue("FirstName");

        //end of making columns,



    }

    public void addTableItems(ArrayList<invoiceOBJ> invoiceDataBase) {
       // System.out.println(Arrays.toString(invoiceDataBase.toArray()) + " table");
        //add invoices list to observable list. (GUI format)
        ObservableList<invoiceOBJ> custList = FXCollections.observableArrayList(invoiceDataBase);
        //Put list to filtered List
        FilteredList<invoiceOBJ> flInvoiceOBJS = new FilteredList(custList, p -> true);

        //add search to filter list values.
        getSearchField().textProperty().addListener(((observable, oldValue, newValue) -> {

            switch (choice.getValue().toString()) {
                case "FirstName":
                    flInvoiceOBJS.setPredicate(p -> p.getCustomer().getFirstName().getValue().toLowerCase().contains(newValue.toLowerCase()));
                    break;
                case "LastName":
                    flInvoiceOBJS.setPredicate(p -> p.getCustomer().getLastName().getValue().toLowerCase().contains(newValue.toLowerCase()));
                    break;
                case "Date":

                    flInvoiceOBJS.setPredicate(p -> p.getGraphicalData("dateOfInvoice").getValue().toLowerCase().contains(newValue.toLowerCase()));
                    break;
                case "Manufacturer":
                    flInvoiceOBJS.setPredicate(p -> p.getGraphicalData("manufacturer").getValue().toLowerCase().contains(newValue.toLowerCase()));
                    break;
                case "ID":
                    flInvoiceOBJS.setPredicate(p -> p.getData().get("id").contains(newValue.toLowerCase()));
                    break;


            }

        }));
        //add search to choiceBox changes.
        choice.valueProperty().addListener(((observable, oldValue, newValue) -> {

            switch (choice.getValue().toString()) {
                case "FirstName":
                    flInvoiceOBJS.setPredicate(p -> p.getCustomer().getFirstName().getValue().toLowerCase().contains(getSearchField().getText().toLowerCase()));
                    break;
                case "LastName":
                    flInvoiceOBJS.setPredicate(p -> p.getCustomer().getLastName().getValue().toLowerCase().contains(getSearchField().getText().toLowerCase()));
                    break;
                case "Date":

                    flInvoiceOBJS.setPredicate(p -> p.getGraphicalData("dateOfInvoice").getValue().toLowerCase().contains(getSearchField().getText().toLowerCase()));
                    break;
                case "Manufacturer":
                    flInvoiceOBJS.setPredicate(p -> p.getData().get("manufacturer").contains(getSearchField().getText().toLowerCase()));
                    break;
                case "ID":
                    flInvoiceOBJS.setPredicate(p -> p.getData().get("manufacturer").contains(getSearchField().getText().toLowerCase()));
                    break;


            }

        }));


        //put filtered list through sorted list
        SortedList<invoiceOBJ> sortedCusts = new SortedList<>(flInvoiceOBJS);
        //add comparators (date)
        sortedCusts.comparatorProperty().bind(invoices.comparatorProperty());
        //finally, add the items to the table view to show
        invoices.setItems(sortedCusts);
    }

private class pageButtonListener implements EventHandler<MouseEvent>  {
        private main root;
        private int indexIncrementor;

        // for indexing new invoices in sqlitedb
    public pageButtonListener(main root, int indexIncrementor){
        this.root =root;
        this.indexIncrementor = indexIncrementor;

    }

    @Override
    public void handle(MouseEvent event) {
        root.setIndexStart(indexIncrementor);
        root.refreshTable();


    }
}
    public void setStageAndListeners(Stage prime, main root, ArrayList<invoiceOBJ> invoiceDataBase) {
        pageButtonListener listener = new pageButtonListener(root,100);
        pageButtonListener backButtonListener = new pageButtonListener(root,-100);
        main = root;
        mainStage = prime;
        setTable();
        addTableItems(invoiceDataBase);
        next.setOnMouseClicked(listener);
        back.setOnMouseClicked(backButtonListener);



    }

    public void getSelected() {
        invoiceOBJ invoice = invoices.getSelectionModel().getSelectedItem();
    }


    @Override
    public void initialize(URL location, ResourceBundle resources) {
        confirmButton.setOnAction(e -> {
            try {

                invoiceOBJ selectedInvoiceOBJ = invoices.getSelectionModel().getSelectedItem();
                if (selectedInvoiceOBJ == null) {
                    Alert noCustomer = new Alert(Alert.AlertType.ERROR, "No Customer Selected!!");
                    noCustomer.setHeaderText("Error, missing invoiceOBJ");
                    noCustomer.show();
                } else {
                    //send user to view invoice INCOMPLETE
                    customerPage.initialize(mainStage, main, selectedInvoiceOBJ);
                }
            } catch (Exception err) {

                err.printStackTrace();
            }
        });

    }
}

Счет-фактура:

 package main;

import javafx.beans.property.SimpleStringProperty;
import java.util.HashMap;

public class invoiceOBJ {
    private HashMap<String, String> invoiceData;
    private int customerID;
    private String customerIDToString;
    private customer cust = new customer();

    public SimpleStringProperty getGraphicalData(String key){
        //return hashmap key value into graphical data.
        return new SimpleStringProperty(invoiceData.get(key));
    }


    public HashMap<String, String> getData() {
        return invoiceData;
    }
    public int getCustomerID(){
        return customerID;
    }
    public String getCustomerIDToString(){
        return customerIDToString;
    }
    public customer getCustomer(){
        return cust;
    }

    @Deprecated // reminder to change this god awful system of passing several variables.
    public invoiceOBJ(int idOfInvoice,
                      String carModel, String manufacturer,
                      String vin, String condition, String licenseState,
                      String regNum, String stage, String vehicleID, String paintCode,
                      String bodyStyle, String year, String dateOfInvoice, HashMap<String,String> customerData) {


        cust.setData(customerData);
        invoiceData = new HashMap<>();
        this.customerID = Integer.parseInt(customerData.get("id"));
        this.customerIDToString = customerData.get("id");
        invoiceData.put("id", Integer.toString(idOfInvoice));
        invoiceData.put("carModel", carModel);
        invoiceData.put("manufacturer", manufacturer);
        invoiceData.put("vin", vin);
        invoiceData.put("condition", condition);
        invoiceData.put("licenseState", licenseState);
        invoiceData.put("regNum", regNum);
        invoiceData.put("stage", stage);
        invoiceData.put("vehicleID", vehicleID);
        invoiceData.put("paintCode", paintCode);
        invoiceData.put("bodyStyle", bodyStyle);
        invoiceData.put("year", year);
        invoiceData.put("dateOfInvoice", dateOfInvoice);

        //put into hashmap for easy access of data later.

    }
    public invoiceOBJ(HashMap<String, String> data, HashMap<String,String> customerData) {
        invoiceData = new HashMap<>(data);
        cust.setData(customerData);
        this.customerID = Integer.parseInt(customerData.get("id"));
        this.customerIDToString = Integer.toString(customerID);


    }
}

1 Ответ

0 голосов
/ 05 декабря 2018

Произошла утечка памяти, потому что вы создаете новые ChangeListener всякий раз, когда добавляете новые данные.Это проблемный блок:

public void addTableItems(ArrayList<invoiceOBJ> invoiceDataBase) {
    // System.out.println(Arrays.toString(invoiceDataBase.toArray()) + " table");
    //add invoices list to observable list. (GUI format)
    ObservableList<invoiceOBJ> custList = FXCollections.observableArrayList(invoiceDataBase);
    //Put list to filtered List
    FilteredList<invoiceOBJ> flInvoiceOBJS = new FilteredList(custList, p -> true);

    //add search to filter list values.
    getSearchField().textProperty().addListener(((observable, oldValue, newValue) -> {
        switch (choice.getValue().toString()) {
            case "FirstName":
                flInvoiceOBJS.setPredicate(p -> p.getCustomer().getFirstName().getValue().toLowerCase().contains(newValue.toLowerCase()));
                break;
            case "LastName":
                flInvoiceOBJS.setPredicate(p -> p.getCustomer().getLastName().getValue().toLowerCase().contains(newValue.toLowerCase()));
                break;
            case "Date":

                flInvoiceOBJS.setPredicate(p -> p.getGraphicalData("dateOfInvoice").getValue().toLowerCase().contains(newValue.toLowerCase()));
                break;
            case "Manufacturer":
                flInvoiceOBJS.setPredicate(p -> p.getGraphicalData("manufacturer").getValue().toLowerCase().contains(newValue.toLowerCase()));
                break;
            case "ID":
                flInvoiceOBJS.setPredicate(p -> p.getData().get("id").contains(newValue.toLowerCase()));
                break;
        }

    }));
    //add search to choiceBox changes.
    choice.valueProperty().addListener(((observable, oldValue, newValue) -> {

        switch (choice.getValue().toString()) {
            case "FirstName":
                flInvoiceOBJS.setPredicate(p -> p.getCustomer().getFirstName().getValue().toLowerCase().contains(getSearchField().getText().toLowerCase()));
                break;
            case "LastName":
                flInvoiceOBJS.setPredicate(p -> p.getCustomer().getLastName().getValue().toLowerCase().contains(getSearchField().getText().toLowerCase()));
                break;
            case "Date":

                flInvoiceOBJS.setPredicate(p -> p.getGraphicalData("dateOfInvoice").getValue().toLowerCase().contains(getSearchField().getText().toLowerCase()));
                break;
            case "Manufacturer":
                flInvoiceOBJS.setPredicate(p -> p.getData().get("manufacturer").contains(getSearchField().getText().toLowerCase()));
                break;
            case "ID":
                flInvoiceOBJS.setPredicate(p -> p.getData().get("manufacturer").contains(getSearchField().getText().toLowerCase()));
                break;


        }
    }));

    //put filtered list through sorted list
    SortedList<invoiceOBJ> sortedCusts = new SortedList<>(flInvoiceOBJS);
    //add comparators (date)
    sortedCusts.comparatorProperty().bind(invoices.comparatorProperty());
    //finally, add the items to the table view to show
    invoices.setItems(sortedCusts);
}

Давайте посмотрим, почему с пошаговым анализом.

  1. Вы создали новый ObservableList с именем custList, затем завернулис FilteredList.
  2. Вы добавили ChangeListener к двум свойствам на двух элементах управления.Внутри этих слушателей вы позвонили FilteredList.setPredicate(Predicate).Когда вы ссылаетесь на FilteredList таким образом, ChangeListener должен сохранять строгую ссылку на FilteredList, без сохранения сильных ссылок он неожиданно выдаст NullPointerException во время выполнения, если список окажется собранным мусором.
  3. Вы позвонили TableView.setItems(), чтобы применить новые отфильтрованные, отсортированные элементы.

Кажется, все работает, но ...

Вы нажали "далее""и загрузил новый набор данных, который вызывает этот точный метод снова .

Это означает, что вы создадите другой ObservableList, обернутый другой FilteredList.Затем добавьте ChangeListener (x2) к тем же свойствам снова .Слушатели собираются держать ссылку на новый FilteredList.Затем вы устанавливаете элементы для таблицы снова .

Когда вы снова вызываете TableView.setItems(), вы фактически отбрасываете старый список.Вы думали, что в конечном итоге это будет сбор мусора, но ... Ваши слушатели все еще в двух свойствах!Напомним, что все эти старые слушатели содержат ссылки на старый список.

Вам нужно создать все эти шаги один раз, без добавления каких-либо данных .Когда данные поступят, просто позвоните TableView.getItems().setAll(newList).Таким образом, вы не будете заново все переделывать и будете хранить дополнительные ссылки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...