Подтвердить что ты не робот

Очистить слушатели свойств и привязки JavaFX (утечки памяти)

Я не нашел простого ответа на эти два вопроса:

1, нужно ли удалить прослушиватель перед удалением экземпляра свойства (слушатель больше нигде не используется)?

BooleanProperty bool = new SimpleBooleanProperty();
bool.addListener(myListener);
bool.removeListener(myListener); // is it necessary to do this?
bool = null;

2, нужно ли отменить однонаправленное ограниченное свойство перед удалением экземпляра свойства?

BooleanProperty bool = new SimpleBooleanProperty();
bool.bind(otherBool);
bool.unbind(); // is it necessary to do this?
bool = null;
4b9b3361

Ответ 1

Случай 1

Учитывая, что myListener "нигде не используется", и поэтому я предполагаю, что локальная переменная [method-], ответ нет. В общем случае, однако, ответ в большинстве случаев - нет, но иногда может быть да.

Пока myListener может достигать цели, он никогда не будет допущен к финализации, и он будет продолжать потреблять память. Например, это будет так, если myListener является "обычно" объявленной переменной static (* все "нормальные" ссылки в Java являются сильными ссылками *). Однако, если myListener - локальная переменная, то после возврата текущего вызова метода объект будет недоступен, а bool.removeListener(myListener) - немного бессмысленная чрезмерная инженерия. Как наблюдатель, так и наблюдаемый выходят за рамки и в конечном итоге будут доработаны. Цитата из моего собственного сообщения в блоге об этом ответе может нарисовать лучшую картину:

Не имеет значения, знает ли коробка о кошке внутри нее, если вы бросьте коробку в океан. Если ящик недоступен, кот.

Теория

Чтобы полностью понять ситуацию здесь, мы должны напомнить себе о жизненном цикле объекта Java (источник):

Объект сильно доступен, если он может быть достигнут некоторой нитью без прохождения каких-либо ссылочных объектов. Недавно созданный объект сильно достижимый потоком, который его создал. [..] Объект слабо достижимый, если он сильно [..] доступен, но может быть достигнутый путем прохождения слабой ссылки. Когда слабые ссылки на слабодоступный объект очищается, объект становится пригодным для завершение.

В случае статических переменных они всегда будут доступны до тех пор, пока класс загружен, таким образом достижимый. Если бы мы не хотели, чтобы статическая ссылка была той, которая мешала сборщику мусора выполнять свою работу, тогда мы могли бы объявить переменную вместо WeakReference, JavaDoc говорит:

Слабые ссылочные объекты [..] не мешают их референтам быть был завершен, финализирован, а затем исправлен. [..] Предположим, что сборщик мусора определяет в определенный момент времени, что объект слабо достижимо. В то время он будет атомизировать все слабые ссылки на этот объект [..]. В то же время он объявит все из ранее недостижимых объектов, подлежащих уточнению.

Явное управление

Для иллюстрации предположим, что мы пишем игру моделирования пространства JavaFX. Всякий раз, когда наблюдаемая планета перемещается в точку зрения наблюдателя космического корабля, игровой движок регистрирует космический корабль с планетой. Совершенно очевидно, что всякий раз, когда планета выходит из поля зрения, игровой движок должен также удалить космический корабль в качестве наблюдателя планеты, используя Observable.removeListener(). В противном случае, по мере того, как космический корабль продолжает летать в пространстве, память течет. В конце концов, игра не сможет обработать пять миллиардов наблюдаемых планет, и она сработает с OutOfMemoryError.

Обратите внимание, что для подавляющего большинства слушателей JavaFX и обработчиков событий их жизненный цикл параллелен циклу их наблюдения, поэтому разработчику приложения не о чем беспокоиться. Например, мы могли бы построить TextField и зарегистрировать в текстовом поле textProperty прослушиватель, который проверяет ввод пользователя. Пока текстовое поле склеивается, мы хотим, чтобы слушатель встал. Рано или поздно текстовое поле больше не используется, и когда он собирает мусор, слушатель проверки также собирает мусор.

Автоматическое управление

Чтобы продолжить пример моделирования пространства, предположим, что наша игра имеет ограниченную многопользовательскую поддержку, и все игроки должны наблюдать друг друга. Возможно, каждый игрок держит локальную таблицу показателей убийства или, возможно, им нужно наблюдать за широковещательными чат-сообщениями. Причина здесь не в этом. Что произойдет, когда игрок покинет игру? Очевидно, что если слушатели не будут явно управляться (удалены), то игрок, который уходит, не будет иметь право на финализацию. Другой игрок сохранит сильную ссылку на автономного игрока. Явное удаление слушателей будет по-прежнему действительным вариантом и, вероятно, самым предпочтительным для нашей игры, но пусть говорят, что он немного навязчив, и мы хотим найти более гладкое решение.

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

Однако просто перенос чего-то в WeakReference - это не все решение. Это редко бывает. Верно, что когда последняя сильная ссылка на "референт" установлена ​​на null или иначе становится недоступной, референт будет иметь право на сбор мусора (при условии, что референт не может быть достигнут с помощью SoftReference). Но WeakReference все еще висит вокруг. Разработчик приложения должен добавить некоторую сантехнику, чтобы сам WeakReference удалялся из структуры данных, в которую он был помещен. Если нет, тогда мы могли бы уменьшить интенсивность утечки памяти, но утечка памяти все равно будет присутствовать, потому что динамически добавляется слабый ссылки тоже потребляют память.

К счастью для нас, JavaFX добавил интерфейс WeakListener и класс WeakEventHandler в качестве механизма для "автоматическое удаление". Конструкторы всех связанных классов принимают действительный слушатель/обработчик, как это предусмотрено клиентским кодом, но они хранят прослушиватель/обработчик, используя слабую ссылку.

Если вы посмотрите на JavaDoc WeakEventHandler, вы заметите, что реализация класса EventHandler, поэтому WeakEventHandler можно использовать везде, где ожидается EventHandler. Аналогично, известная реализация WeakListener может использоваться везде, где ожидаются InvalidationListener или ChangeListener.

Если вы посмотрите на исходный код WeakEventHandler, вы заметите, что класс в основном является только оболочкой. Когда его референт (обработчик реального события) собирает мусор, WeakEventHandler "перестает работать", не делая ничего вообще, когда вызывается WeakEventHandler.handle(). WeakEventHandler не знает, к какому объекту он подключен, и даже если он это сделал, удаление обработчика событий не является однородным. Однако все известные классы реализации WeakListener имеют конкурентное преимущество. Когда их обратные вызовы вызываются, они неявно или явно предоставляют ссылку на наблюдаемые, с которыми они зарегистрированы. Поэтому, когда референтом WeakListener является сборщик мусора, в конечном итоге реализация WeakListener будет гарантировать, что сам WeakListener будет удален из наблюдаемого.

Если это еще не ясно, решение для нашей игры по космическому моделированию должно позволить игровому движку использовать сильные ссылки на все онлайн-космические корабли. Когда космический корабль выходит в интернет, все остальные онлайн-космические корабли регистрируются новым игроком с использованием слабого слушателя, такого как WeakInvalidationListener. Когда игрок переходит в автономный режим, игровой движок удаляет свою сильную ссылку на игрока, и игрок получает право на сбор мусора. Игровому движку не нужно беспокоиться о явном удалении автономного игрока в качестве слушателя других игроков.

Случай 2

Нет. Чтобы лучше понять, что я скажу дальше, сначала прочтите мой вопрос 1.

BooleanPropertyBase хранит сильную ссылку на otherBool. Это само по себе не приводит к тому, что otherBool всегда может достижимо и, следовательно, потенциально может вызвать утечку памяти. Когда bool становятся недоступными, тогда все его хранимые ссылки (при условии, что они больше нигде не хранятся).

BooleanPropertyBase также работает, добавляя себя в качестве наблюдателя того свойства, с которым вы его связываете. Тем не менее, он делает это, завернувшись в класс, который работает почти так же, как WeakListener, описанный в моем случае 1 ответ. Поэтому, как только вы аннулируете bool, это будет только вопрос времени, прежде чем он будет удален из otherBool.

Ответ 2

Я полностью согласен с ответом case 1, но case 2 немного сложнее. Требуется вызов bool.unbind(). Если опустить, делает причиной небольшой утечки памяти.

Если вы запустите следующий цикл, приложение в конечном итоге закончится без памяти.

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

В BooleanPropertyBase, intenally, не используется реальный WeakListener (реализация интерфейса WeakListener), он использует полу- запеченный раствор. Все экземпляры "p2" получают в конечном итоге сбор мусора, но слушатель, содержащий пустой WeakReference, остается в памяти навсегда для каждого "p2". То же самое относится ко всем свойствам, а не только к BooleanPropertyBase. Он подробно объяснил здесь, и они говорят, что он исправлен в Java 9.

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