Czyste słuchacze i powiązania właściwości JavaFX (wycieki pamięci)

Nie znalazłem prostej odpowiedzi na te dwa pytania:

  1. Czy muszę usunąć listener przed usunięciem instancji właściwości (listener nie jest używany nigdzie indziej)?

    BooleanProperty bool = new SimpleBooleanProperty();
    bool.addListener(myListener);
    bool.removeListener(myListener); // is it necessary to do this?
    bool = null;
    
  2. Czy muszę odłączyć jednokierunkową ograniczoną właściwość przed usunięciem instancji właściwości?

    BooleanProperty bool = new SimpleBooleanProperty();
    bool.bind(otherBool);
    bool.unbind(); // is it necessary to do this?
    bool = null;
    
Author: Lonely Neuron, 2013-01-28

2 answers

Przypadek 1

Biorąc pod uwagę, że myListener "nie jest używana nigdzie indziej" i dlatego zakładam, że zmienna lokalna [metoda -], odpowiedzią jest no . W ogólnym przypadku odpowiedź jest najczęściej Nie, ale czasami może być tak.

Dopóki myListener jest silnie osiągalny, nigdy nie będzie się kwalifikował do finalizacji i będzie nadal zużywał pamięć. Na przykład, będzie tak, jeśli myListener jest" normalnie " zadeklarowaną zmienną static (*all "normalne" odniesienia w Javie to silne odniesienia*). Jeśli jednak myListener jest zmienną lokalną, to obiekt nie będzie już osiągalny po zwróceniu bieżącego wywołania metody i bool.removeListener(myListener) jest nieco bezsensowną nadinżynierią. Zarówno obserwator, jak i Observable wychodzą poza zasięg i ostatecznie zostaną sfinalizowane. Cytat z mojego własnego posta na blogu o tej odpowiedzi może namalować lepszy obraz:

Nie ma znaczenia, czy pudełko wie o kocie w środku to, jeśli Wrzuć pudełko do oceanu. Jeśli pudełko nie jest osiągalne, ani nie jest kot.

Teoria

Aby w pełni zrozumieć sytuację, musimy przypomnieć sobie cykl życia obiektu Java (źródło):

Obiekt jest silnie osiągalny, jeśli można do niego dotrzeć przez jakiś wątek bez przechodzenia przez obiekty referencyjne. Nowo utworzony obiekt jest silnie osiągalny przez wątek, który go stworzył. [..] Obiekt jest słabo osiągalny, jeśli jest [nie] mocno [..] osiągalny, ale może być / align = "left" / Gdy słabe odniesienia do słabo osiągalny obiekt jest wyczyszczony, obiekt staje się kwalifikowany do finalizacja.

W przypadku zmiennych statycznych, będą one zawsze dostępne tak długo, jak klasa jest załadowana, a więc osiągalne. Jeśli nie chcemy, aby statyczne odniesienie było tym, które utrudnia garbage collector do jego pracy, wtedy możemy zadeklarować zmienną, aby używała WeakReference zamiast tego. JavaDoc says:

Słabe obiekty odniesienia [..] nie uniemożliwiają ich referentom gotowe do finalizacji, sfinalizowane, a następnie odzyskane. [..] Załóżmy, że garbage collector określa w pewnym momencie w czasie, że obiekt jest słabo osiągalny. W tym czasie będzie atomicznie wyczyścić wszystkie słabe odniesienia do tego obiektu [..]. Jednocześnie zadeklaruje wszystkie z wcześniej słabo osiągalnych obiektów, które mają być finalizowalny.

Jawne zarządzanie

Dla ilustracji Załóżmy, że piszemy grę JavaFX space simulation. Gdy planeta porusza się w widoku obserwatora statku kosmicznego, silnik gry rejestruje statek kosmiczny wraz z planetą. Jest całkiem oczywiste, że ilekroć planeta wypada z widoku, silnik gry powinien również usunąć statek kosmiczny jako obserwatora planety za pomocą Observable.removeListener(). W przeciwnym razie, jak statek kosmiczny nadal latać w przestrzeni, pamięć będzie wyciekać. Ostatecznie gra nie poradzi sobie z pięcioma miliardami obserwowanych planet i rozbije się z OutOfMemoryError.

Należy pamiętać, że dla zdecydowanej większości słuchaczy JavaFX i programów obsługi zdarzeń ich cykl życia jest równoległy do cyklu ich Observable, więc twórca aplikacji nie ma się czym martwić. Na przykład, możemy skonstruować TextField i zarejestrować w polu tekstowym textProperty słuchacz, który weryfikuje dane wejściowe użytkownika. Dopóki pole tekstowe będzie się trzymać, chcemy, żeby słuchacz został. Wcześniej czy później pole tekstowe nie jest już używane, a gdy jest zbierane śmieci, słuchacz walidacji jest również zbierany śmieci.

Automatyczne zarządzanie

Aby kontynuować przykład symulacji kosmicznej, Załóżmy, że nasza gra ma ograniczone wsparcie dla wielu graczy i wszyscy gracze muszą obserwować siebie nawzajem. Być może każdy gracz zachowuje lokalną tablicę wyników z metrykami kill lub być może musi obserwować wiadomości na czacie. Powód nie jest to tu ważne. Co się stanie, gdy gracz zrezygnuje z gry? Oczywiście, jeśli słuchacze nie są bezpośrednio zarządzani (usunięty ), wtedy gracz, który zrezygnuje, nie będzie kwalifikował się do finalizacji. Drugi gracz zachowuje silne odniesienie do gracza offline. Wyraźne usunięcie słuchaczy nadal byłoby ważną opcją i prawdopodobnie najbardziej preferowanym wyborem dla naszej gry, ale powiedzmy, że wydaje się to nieco natrętne i chcemy znaleźć bardziej gładki rozwiązanie.

Wiemy, że silnik gry utrzymuje silne odniesienia do wszystkich graczy online, tak długo, jak są online. Dlatego chcemy, aby statki kosmiczne słuchały zmian lub wydarzeń o sobie tylko tak długo, jak silnik gry zachowuje silne odniesienia. Jeśli czytasz sekcję "teoria", to z pewnością WeakReference brzmi jak rozwiązanie.

Jednak samo owijanie czegoś w słabą referencję nie jest całym rozwiązaniem. Rzadko tak jest. To prawda, że kiedy ostatni mocny odniesienia aby "referent" był ustawiony na null lub w inny sposób stał się nieosiągalny, referent będzie uprawniony do zbierania śmieci (zakładając, że referent nie może być osiągnięty za pomocą SoftReference). Ale słaba Referencja wciąż się kręci. Twórca aplikacji musi dodać trochę kanalizacji, aby sama słaba Referencja została usunięta ze struktury danych, w której został umieszczony. Jeśli nie, możemy zmniejszyć nasilenie wycieku pamięci, ale wyciek pamięci nadal będzie obecny ponieważ dynamicznie dodawane słabe odwołania zużywają również pamięć.

Na szczęście dla nas JavaFX dodał interfejs WeakListener i klasa WeakEventHandler jako mechanizm "automatycznego usuwania". Konstruktory wszystkich powiązanych klas akceptują rzeczywisty listener / handler dostarczany przez kod klienta, ale przechowują listener / handler używając słabego odniesienia.

Jeśli spojrzysz na JavaDoc WeakEventHandler, zauważysz, że klasa implementuje EventHandler, więc weakeventhandler może być użyty wszędzie tam, gdzie spodziewany jest EventHandler. Podobnie, znana implementacja WeakListener Może być używana wszędzie tam, gdzie oczekuje się InvalidationListener lub ChangeListener.

Jeśli przyjrzysz się kodowi źródłowemu WeakEventHandler, zauważysz, że klasa jest w zasadzie tylko opakowaniem. Kiedy jego referent (the real event handler ) jest zbierany, WeakEventHandler "przestaje działać", nie robiąc nic, gdy WeakEventHandler.handle() nazywa się. WeakEventHandler nie wie, z którym obiektem został spiknięty, a nawet jeśli tak, usunięcie obsługi zdarzenia nie jest jednorodne. Wszystkie znane klasy implementujące WeakListener mają jednak przewagę konkurencyjną. Gdy wywołane są ich wywołania zwrotne, są one w sposób dorozumiany lub jawny dostarczane odniesienie do Observable, w którym są zarejestrowane. Kiedy więc referent {[17] } zostanie odebrany, ostatecznie implementacja WeakListener upewni się, że sama WeakListener zostanie usunięta z Observable.

Jeśli nie jest już jasne, rozwiązanie dla naszego kosmiczna gra symulacyjna polegałaby na tym, aby silnik gry używał silnych odniesień do wszystkich statków kosmicznych online. Kiedy statek kosmiczny przechodzi w tryb online, wszystkie inne statki kosmiczne online są rejestrowane z nowym graczem za pomocą słabego słuchacza, takiego jak WeakInvalidationListener. Gdy gracz przechodzi w tryb offline, silnik gry usuwa jego silne odniesienie do gracza, a gracz będzie uprawniony do zbierania śmieci. Silnik gry nie musi się martwić o wyraźne usunięcie gracza offline jako słuchacza innych graczy.

Przypadek 2

Nie. aby lepiej zrozumieć, co powiem dalej, przeczytaj najpierw mój przypadek 1 ODPOWIEDŹ.

BooleanPropertyBase przechowuj silne odniesienie do otherBool. To samo w sobie nie powoduje, że otherBool jest zawsze osiągalny, a tym samym potencjalnie powoduje wyciek pamięci. Kiedy bool staje się nieosiągalny, wtedy wszystkie jego przechowywane odwołania (zakładając, że nie są przechowywane nigdzie indziej).

BooleanPropertyBase działa również poprzez dodanie się jako Observer własności, do której ją przywiązujesz. Jednak robi to, owijając się w klasę, która działa prawie dokładnie tak, jak WeakListener s opisane w moim przypadku 1 ODPOWIEDŹ. Kiedy więc anulujesz bool, będzie tylko kwestią czasu, zanim zostanie ona usunięta z otherBool.

 27
Author: Martin Andersson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-17 01:04:52

Całkowicie zgadzam się z odpowiedzią case 1 , ale case 2 jest nieco bardziej skomplikowana. The bool.unbind() połączenie jest konieczne. Jeśli ommitted, to powoduje mały wyciek pamięci.

Jeśli uruchomisz następującą pętlę, aplikacja w końcu skończy się pamięć.

BooleanProperty p1 = new SimpleBooleanProperty();
while(true) {
    BooleanProperty p2 = new SimpleBooleanProperty();
    p2.bind(p1)
}

BooleanPropertyBase, intenally, nie używa prawdziwego WeakListener( implementacja interfejsu WeakListener), używa do połowy upieczonego rozwiązania. Wszystkie instancje " p2 " otrzymują ostatecznie zbierane śmieci, ale słuchacz trzymający pustą Weakreferencję pozostaje w pamięci na zawsze dla każdego "p2". To samo dotyczy wszystkich właściwości, nie tylko BooleanPropertyBase. jest to szczegółowo wyjaśnione tutaj i mówią, że jest to naprawione w Javie 9.

W większości przypadków nie zauważasz tego wycieku pamięci, ponieważ pozostawia on tylko kilkadziesiąt bajtów na każde niezwiązane Wiązanie. Ale w niektórych przypadkach sprawiało mi to prawdziwe kłopoty. Dobrym przykładem są komórki tabeli z tabela, która jest często aktualizowana. Komórki następnie ponownie wiążą się z różnymi właściwościami przez cały czas, a te resztki w pamięci gromadzą się szybko.

 6
Author: Jan X Marek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-17 01:05:25