Usuwanie z pamieci zagnieżdżonych obiektów Java

0

Hej,

Mam problem z moim programem do rysowania drzewa. Aby je narysowac uzywam bardzo zagnieżdzonej struktury. Obiekt w obiekcie i w obiekcie obiekt ;) , a dotego dużo listenerów.
Zauwayłam, że przy każdym przerysowaniu ilośc uzywanej pamieci wzrasta o 100 MB. Ewidentnie Java nie jest w stanie wyczyścić starych obiektów.

Zaczełam sobie przygotowywac funkcję clearujacą referencje ( chociaż nie bardzo mi się takie rozwiazanie podoba), ale nie zauważyłam, żadnej poprawy. Mam zatem pytanie :

Dla class zawierajacych takie obiekty

    private final PanelChild panelChild;
    private final Line lineRelationToSim;
    private final Line lineSimConnectAllEx;
    private final Line lineSimConnectSpouse;

Czy aby obiekt tej klasy nadrzednej mógł byc usunięty z pamięci, powinnam usunąc także referencje do panelChild ?? Czyli ta zmienna nie może być final?? To zamo z obiektami Line ??

2

Na 99% szukasz problemu w złym miejscu. Jeśli GC nie zwalnia obiektów to znaczy że te obiekty są gdzieś "dostępne" i zwolnić ich nie można. Jeśli np. robisz coś w stylu:

  • dodajesz listenera
  • listener trzyma sobie referencje do obiektu X

To dopóki nie usuniesz tego listenera, obiekt X nie zostanie zwolniony. Przypuszczam że u ciebie właśnie taka sytuacja ma miejsce.

Czy aby obiekt tej klasy nadrzednej mógł byc usunięty z pamięci, powinnam usunąc także referencje do panelChild

Nie, zupełnie nie ma takiej potrzeby. Usunięcie referencji do obiektu nadrzędnego spowoduje zwolnienie tych obiektów, o ile nie masz gdzieś indziej to nich odwołania.

0

Hej,
Dokonałam dość mocnego refactoringu kodu. We wszystkich klasach, w których dodaj pory dodawałam listenery przez

observablevalue.addListener((obs, oldV, newv) -> {
  (...)
})

Stworzyłam dla każdego listenera zmienną, i to tę zmienną dodaje przy inicjalizacji klasy o usuwam przy wywolaniu metody clean.
Metoda clean została stworzona i wywołuje ją kaskadowo. Niestety bez rezultatu. (a czasem mam wrazenie ze jest jeszcze gorzej)...

Ktos mógłby zerknąć ? https://github.com/Vanilka/GenealogyTree/tree/GenTreev3-final/GenealogyTreeProject

pakiety: gentree.client.visualization.elements.gustave
z tego pakietu uzywane sa przede wszystkim klasy :
-FamilyGroup
-FamilyMember
-RelationReference
-RelationTypeElement
-HeaderPane (opcjonalnie)

gentree.client.visualization.gustave .
Tutaj w zasadzie wszystkie klasy

W akcie desperacji po uruchomieniu cleana zaczelam nawet nullowac pola...

1

kowalski opcje.

  1. Robisz dump'a i analizujesz go przez Eclipse Memory analyser ( najprawdopodobniej sam CI powie gdzie leży problem)
  2. Szybka analiza przez VisualVM

Jeżeli Java nie potrafi zgarnąć obiektów to znaczy że masz do nich referencje.

EDIT

co muszę kliknąć żeby dojść do takiej sytuacji którą opisujesz - bo ta appka (swoją drogą najs UI) jest dość skomplikowana :D

P.S

nie zgaduj - tu nie ma magii (trochę jest, ale to zazwyczaj bliżej kernela) - znajdź root cause

0

Ciesze sie ze UI ci sie podoba ;) chociaz ja jeszcze nie ejstem znniego w pelni zadowolona.

Nie uzywam eclipsa. Wlasnie zastanawiałam się jakim narzędziem moge sobie podglądac co się w pamięci dzieje.

0

Eclipse memory analyzer to tool stworzony przez SAP AG - wyrzucony jako open source i po prostu opiera się o eclipsa .

Wlasnie zastanawiałam się jakim narzędziem moge sobie podglądac co się w pamięci dzieje.

Eclipse memory analyzer

inną alternatywa jest Eclipse memory analyzer albo Eclipse memory analyzer

Tak naprawdę to analizując memory dumpa nie możesz popatrzeć sobie co dzieje się w pamięci. Możesz zobaczyć co znajduje się na heapie w momencie jego wykonania.

hsdb jest toolem developerskim który rozpowszechniony jest razem z VMkami pochodnymi Hotspotu i można chodzić sobie po adressach na universie (universem w hotspot nazywa się cały heap) - ale jest to totalnie nie użyteczne z punktu widzenia developera Javy i trzeba mieć naprawdę przerytą banie żeby tego używać, niemniej jak ktoś kiedyś tu trafi to https://gist.github.com/rednaxelafx/4986917

0

Ok pobawiłam sie troche tym VisualVM ( nie zjedzcie mnie.. ale wczensiej o nim nie wiedziałam) .. i faktycznie widze ze te obiekty zostają.. Widzę że jakieś lambdy nie są czyszczone.
https://imgur.com/ETLkV3H
https://imgur.com/SDUWX6c
https://imgur.com/tARWiPb
Te 213 obiektow (powinno byc 74) oraz 3 lambdy, teoretycznie zgadzałyby się z 3 listenerami. Ale ja je przecież czyszczę.. więc nie mam pojęcia co mogę zrobić wiecej

0
Vanilka napisał(a):

Ok pobawiłam sie troche tym VisualVM ( nie zjedzcie mnie.. ale wczensiej o nim nie wiedziałam) .. i faktycznie widze ze te obiekty zostają.. Widzę że jakieś lambdy nie są czyszczone.
https://imgur.com/ETLkV3H
https://imgur.com/SDUWX6c
https://imgur.com/tARWiPb
Te 213 obiektow (powinno byc 74) oraz 3 lambdy, teoretycznie zgadzałyby się z 3 listenerami. Ale ja je przecież czyszczę.. więc nie mam pojęcia co mogę zrobić wiecej

Robisz memorry dumpa, wrzucasz do Eclipsa i patrzysz kto referuje do tych obiektów :)

0

Patrząc w przykładowe wyniki (i te lambdy z klasy BetweenChildrenConnector) ... a jak klikniesz w PerformGC to nadal to zostaje w pamięci ? o_O

0
jarekr000000 napisał(a):

Patrząc w przykładowe wyniki (i te lambdy z klasy BetweenChildrenConnector) ... a jak klikniesz w PerformGC to nadal to zostaje w pamięci ? o_O

To jest taka ilość bajtów, że to powinien być problem po tygodniu na produkcji ;D

Ciekawostka : Wykonanie PerformGC spowoduje full-gc, czyli najpierw young + old, monolitycznie, czyli czyścimy cały heap, na produkcji, w warunach bojowych może wystąpić taka sytuacja (której wykonaniem PerformGC nie wystalkujesz ;D)

  1. old : [] young : [a->b->c->d]
    wykonujemy minor GC, obiekt (a) był w young gen 16cykli i przechodzi do old'a mając referencje do b
  2. old : [a] young : [b->c->d] (a, referuje do b)
    (a) umiera,
  3. wykonujemy minor GC i teraz minor garbage collector wie tylko tyle że w olddzie jest obiekt który ma referencje do young'a, i nie zwolni pamięci (taka optymalizacja przez card-table).
    ten schemat może się teraz dla tego czejna powtarzać - nasze obiekty a->b->c->d to queue.

nazywa się to gc nepotism :)

0

Problem nadal nie rozwiazany mimo moich usilnych prób ;(

0

Pisał Ci Karp:

co muszę kliknąć żeby dojść do takiej sytuacji którą opisujesz

Nie odpisałaś, więc nie da się nic sprawdzić.

0
jarekczek napisał(a):

Pisał Ci Karp:

co muszę kliknąć żeby dojść do takiej sytuacji którą opisujesz

Nie odpisałaś, więc nie da się nic sprawdzić.

A nie .. Ja mu odpisalam ale na priv.

https://github.com/Vanilka/GenealogyTree/tree/GebTreev3-memory/GenealogyTreeProject

W folderze "TestProjects" znajduje sie przykladowy XML z projektem. Bo skombilowaniu aplikacji w miejscu jej uruchomienia utworza sie niezbedne foldery. Do folderu projects trzeba wrzucic ten przykładowy XML.

Po otworzeniu aplikacji wybrać Local Application i otworzyć projekt z pliku. Nastepnie dodac nowa osobe.
Przy kazdym dodaniu nowej osoby drzewo sie przerysuje ( lub dodaniu relacji). I za kazdym przerysowaniem zobaczycie jak pamiec wariuje :D

Zawsze moge napisac w wymaganiach aplikacji ze potrzebny jest super komputer z 10 rdzeniami i 1TB ramu :D ale chcialabym tego uniknac ;P

2

Uruchomiłem visualvm i zauważyłem, że w pamięci zostają jakieś elementy JavaFX. W ogóle na początku podejrzewam framework graficzny, bo on musi dużo rzeczy przechowywać i wystarczy przegapić jakiś jeden dispose, żeby zapychał pamięć. Na Swingu jest podobnie.

Potem patrzyłem, kiedy te elementy powstają. Sprawdzałem w kolejnych dumpach, jakie obiekty się pojawiają, a jakie zostają, choć powinny znikać. Zwróciłem uwagę na ScreenMainRightController. Po wpisaniu w visualvm w widoku Classes, w filtrze: RightCon - widziałem, ile tych kontrolerów jest utworzonych. I widziałem, że ich liczba rośnie. Dla odmiany liczba kontrolerów ScreenMainController była ciągle równa 1.

Należało więc przyjrzeć się miejscu, gdzie powstaje ten Right. No i tam tworzony jest taki obiekt (przez new), do którego referencja natychmiast jest tracona. Czyli bałaganiarstwo, ale niegroźne. Bo przecież element bez referencji natychmiast zostanie zgarnięty przez GC. Chyba że konstruktor gdzieś zostawia ślad po sobie, tworzy jakiś wyciek. No dobra, Right nie ma w ogóle konstruktora, czyli spokój. Ale zaraz, przecież on dziedziczy konstruktor po AnchorPane. No to pewnie AnchorPane (komponent JavaFX), gdzieś się sam rejestruje. Podebugowałem trochę JavaFX, dzięki Intellijejowi, który ładnie dekompiluje kod. I znalazłem takie miejsce: w Node.java:

pickOnBounds = new SimpleBooleanProperty(this, "pickOnBounds");

Tutaj zachowuje się referencja do naszego kontrolera. Nigdy nie robimy na nim close(), więc zostaje w pamięci do końca. W zasadzie trudno zrobić close, jak taka metoda nie istnieje :)

Rzeczywiście, visualvm wskazywał te miejsca w widoku: ScreenMainRightController, Show In Instances View, Show Nearest GC Root. Były tam wymienione i Node i SimpleBooleanProperty. Czyli wszystko się zgadza.

Pozostaje więc zadbać o to, by nie tworzyć 2 kontrolerów, a 1. Powinno się uspokoić. ScreenManager.loadFxml - tu gubiony jest stary kontroler i tworzony od razu nowy. Usuwam więc parametr controller i usuwam ten argument ze wszystkich wywołań. Niestety, to nie wystarcza.

Patrzę jeszcze na akcję zamknięcia projektu, closeProject(). Nie widzę tu żadnego zamknięcia okien. A przy otwieraniu projektu robisz ciągle FXMLLoader.load. Coś tu jest nie tak, ale nie powiem co, bo pierwszy raz oglądam aplikację JavaFX.

0
jarekczek napisał(a):

Hej.. sam ten kontroler nawet nie ejst problematyczny. Przyjzyj sie bardziej elementom z paczek visualization. Tam sa wszystkie Panele oraz FamiliMember. Probowałam zrobic aby chociaz FamilyMember nie zostawały ( isc od dolu bo to jest jakby najmniejszy atomowy element).. ale nic z tego, zostaja.

0

Nie znikną, jeżeli kontroler je trzyma.

0

Hmm no to czegos tu nie rozumiem.. .mam Controller -> Hbox -> InnyPanel -> FamilyMember.
Myslalam ze to działa w drugą strone.. ze najpierw musi zniknąć FamilyMember aby mógł zniknąc Inny panel.. itd

Nie bardzo rozumiem dlaczego wlasnie ten controller jest problematyczny ??

0

Musisz poczytać o zarządzaniu obiektami w JavaFX i zrobić to zgodnie z zasadami sztuki. Może coś o zamykaniu okien poszukać. Potem możesz zgłębiać temat garbage collectora. No i wtedy już wszystko będziesz wiedzieć.

Family Member nie zniknie dopóki wskazuje na niego InnyPanel, tak jak narysowałaś. I tak dalej. Na controller nie wskazujemy my osobiście, robią to bebechy JavaFX. Dopóki nie przestaną, wszystko będzie siedzieć w pamięci.

Można spróbować podejść inaczej do problemu. Niech sobie ten kontroler siedzi w pamięci, ale niech będzie tylko jeden. Nie twórz kolejnych kontrolerów (kolejnych instancji, w load), tylko próbuj korzystać ciągle z tego jednego (w danej klasie). Wtedy pamięć nie będzie się zapychać. To już takie chłopskie rozumowanie, bo jak mówiłem - JavaFX jest mi obca.

0

No wlasnie z tego powodu stworzylam funkcje clearujaca. I raczej myslalam ze dziala to w druga strone.. Jesli InnyPanel zaweira pole FamilyMember, to InnyPanel nie zniknie poki nie zniknie familyMember ... teraz to juz zupelnie nic nie rozumiem jak to mam zrobic.

Sprawdizlam tez u siebie ten ScreenMainRight i u mnie jest caly czas jeden.

@Edit:
Teraz patrze, ze ten problem dotyczy kazdego okna.. kazdego dialogu.. itd.. nic sie nie clearuje :/

--

0

Problem w JavieFX jest taki ze uzywajac Loaderow jak w Screenmanagerze, nie bardzo mozna wykorzystac 2 razy ten sam Controller, poniewaz nie mozna znow odwolac sie do Loadera. I to jest wlasnie problem.

Ok próbuje trochę z tym kombinowac.. I faktycznie czesto jest tak że Controllery sa wywoływane 2 krotnie ... Jest tak za sprawą ScreenManagera poniewaz ladowaniaFXML odbywa się w ten sposob :


 public FXMLTab loadFxml(FXMLTab controller, JFXTabPane tabPane, Tab tab, String fxml) {

        FXMLLoader loader = new FXMLLoader(getClass().getResource(fxml), context.getBundleValue());
        try {
            tab.setContent(loader.load());
            controller = loader.getController();
            controller.setTabAndTPane(tabPane, tab);

        }  catch (Exception ex) {
            log.error(ex.getMessage());
            log.error(ex.getCause());
            ex.printStackTrace();
        }

        return controller;
    }

Czyli podaje notNull w parametrze, a potem nadpisuje ... myslalam ze to bedzie smigać, ale niestety, powoduje ze obydwa sa w pamieci.
Poprawilam to dla jednego z prostszych kontrollerow :

    public void showNewDialog( FilesFXML fxml) {

        FXMLLoader loader = new FXMLLoader(getClass().getResource(fxml.toString()), context.getBundleValue());

        try {
            Stage dialogStage = new Stage();
            AnchorPane dialogwindow = (AnchorPane) loader.load();
            FXMLDialogController controller = loader.getController();
            dialogStage.initModality(Modality.WINDOW_MODAL);
            dialogStage.initOwner(this.getStage());
            Scene scene = new Scene(dialogwindow);
            dialogStage.setScene(scene);
            dialogStage.setResizable(false);
            controller.setStage(dialogStage);
            dialogStage.showAndWait();


        } catch (Exception ex) {
            log.error(ex.getMessage());
            log.error(ex.getCause());
            ex.printStackTrace();
        }
    }

@Log4j2
public class DialogOpenProjectController extends DialogPane implements Initializable, FXMLController, FXMLPane, FXMLDialogController {


    @FXML
    private ObjectProperty<ResourceBundle> languageBundle = new SimpleObjectProperty<>();

    @FXML
    private JFXTabPane TAB_PANE_OPEN_PROJECT;

    @FXML
    private JFXButton BUTTON_CONFIRM;

    @FXML
    private JFXButton BUTTON_CANCEL;

    private Tab tabOpenNewProject;
    private Tab tabOpenExistingProject;

    private Stage stage;
    private ChangeListener<? super ResourceBundle> languageListener = this::languageChange;
    private ChangeListener<? super Tab> selectedTabListener = this::selectedTabChanged;

    {
        tabOpenExistingProject = new Tab();
        tabOpenNewProject = new Tab();
    }

    /**
     * Initializes the controller class.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        log.trace(LogMessages.MSG_CTRL_INITIALIZATION);
        this.languageBundle.setValue(rb);
        initTabs();

        addSelectedTabListener();
        addDisableButtonListener();
        addLanguageListener();

        log.trace(LogMessages.MSG_CTRL_INITIALIZED);
    }
    @FXML
    public void cancel() {

        this.stage.close();
        clean();
    }

    @FXML
    public void confirm() {
        if (TAB_PANE_OPEN_PROJECT.getSelectionModel().getSelectedItem().equals(tabOpenNewProject)) {
            actionNewProject();

        } else if (TAB_PANE_OPEN_PROJECT.getSelectionModel().getSelectedItem().equals(tabOpenExistingProject)) {
            actionOpenProject();
        } else {

            //To to show error
        }
        this.stage.close();
        clean();

    }

    private void actionNewProject() {
//nothing to do for the moment
    }

    private void actionOpenProject() {
//Nothing to do for the moment
    }


    /**
     * Initialization of Tabs </br>
     * If Service is GenTreeLocalService the Open existing tab will be initialized
     */
    private void initTabs() {
        
    }

    /**
     * Initialization of Tab listener
     */
    private void addSelectedTabListener() {
        TAB_PANE_OPEN_PROJECT.getSelectionModel().selectedItemProperty().addListener(selectedTabListener);
    }

    private void addDisableButtonListener() {
  //nothing to do at the moment
    }


    private void clean() {
        this.languageBundle.removeListener(languageListener);

        languageBundle.unbind();
        languageBundle.setValue(null);
        TAB_PANE_OPEN_PROJECT.getSelectionModel().selectedItemProperty().removeListener(selectedTabListener);
        TAB_PANE_OPEN_PROJECT.getSelectionModel().clearSelection();
        TAB_PANE_OPEN_PROJECT.getTabs().clear();
        BUTTON_CONFIRM.disableProperty().unbind();

        selectedTabListener = null;
        languageListener = null;
        tabOpenNewProject = null;
        tabOpenExistingProject = null;
        stage = null;

       getChildren().clear();

    }


    /*
     * LISTEN LANGUAGE CHANGES
     */

    private void addLanguageListener() {
        this.languageBundle.addListener(languageListener);
    }

    private void languageChange(ObservableValue<? extends ResourceBundle> observable, ResourceBundle oldValue, ResourceBundle newValue) {
        reloadElements();
    }

    private String getValueFromKey(String key) {
        return this.languageBundle.getValue().getString(key);
    }

    private void reloadElements() {
        // Nothing to do
    }

    /*
     * GETTERS AND SETTERS
     */


    public void setStage(Stage stage) {
        this.stage = stage;
    }

    private void selectedTabChanged(ObservableValue<? extends Tab> observable, Tab oldValue, Tab newValue) {
        if (newValue.equals(tabOpenNewProject)) {
            BUTTON_CONFIRM.setText(getValueFromKey(Keys.CREATE));
        } else if (newValue.equals(tabOpenExistingProject)) {
            BUTTON_CONFIRM.setText(getValueFromKey(Keys.OPEN));
        }
    }
}

Ta funkcja clearujaca to moje rozpaczliwe proby naprawienia tego :D WIec juz probowalam chyba wszystkiego, a dziadostwo nadal nie chce zniknac.

1 użytkowników online, w tym zalogowanych: 0, gości: 1