Некоторые люди удивляются, узнав, что в языке существует несколько типов ссылок. Обычно они используются при реализации некоторых внутренних компонентов или фреймворков, поэтому большинство людей не имеют к ним прямого доступа. Они также не используются в большинстве Java-приложений, поскольку привносят дополнительный уровень сложности и требуют более глубокого понимания того, как работает сборщик мусора. Однако это не означает, что их не следует использовать, на самом деле, есть случаи использования, когда они являются лучшим вариантом безоговорочно и могут быть элегантным решением сложной проблемы. Они позволяют нам в полной мере использовать GC и его сложные алгоритмы управления памятью и дают расширенные подсказки о том, какой объект должен жить дольше, а какой следует немедленно удалить/собрать.

В языке Java существует четыре типа ссылок.

Сильная ссылка

Это обычная ссылка, которую все используют в своей повседневной работе. Объект, доступный через сильную ссылку, не подходит для сбора. В тот момент, когда мы присваиваем ссылке null или она выходит за пределы области действия и удаляется из стека, GC помечает объект как подходящий для сбора.

Давайте посмотрим на код

В строке 2 происходит несколько вещей. Сначала создается сильная ссылка с именем object и создается новый объект. Затем оператор ‘=’ указывает на ссылку на объект. Ссылка находится в стеке, а объект — в куче.

В строке 3 разрываем соединение. С этого момента объект считается подходящим для коллекции. Однако они не будут собраны до следующего цикла сборки мусора.

Теперь строка 5 делает две вещи. Создает новый объект, а затем указывает ссылку на него. В строке 6 метод завершает работу. Когда метод завершает работу, все переменные, находившиеся в его области действия, удаляются в обратном порядке (отсюда и название стека). Это означает, что в этот момент ссылка на объект перестанет существовать, и объект будет помечен как подходящий для сбора (и, в конечном итоге, собран).

Я предполагаю, что большинство из вас уже знакомы с этим типом справки и с тем, как он работает (это очень распространенный вопрос на собеседовании в конце :D). Я просто хотел обратить ваше внимание на объект приемлемости GC, чтобы мы могли двигаться к другим ссылочным типам.

Слабая ссылка

Слабые ссылки предоставляются нам с классом WeakReference. В отличие от сильных ссылок, они не мешают GC собирать объект.

Журналы в строках 13 и 14 дают нам следующее

1066
java.lang.ref.WeakReference@251a69d7

Давайте тогда проанализируем код. Сначала мы определяем сильную ссылку на объект, а в строке 2 указываем слабую ссылку на него. Затем мы устанавливаем сильную ссылку на null, это сделает объект пригодным для сбора, и у нас будет только слабая ссылка, указывающая на него. Затем мы зацикливаемся до тех пор, пока объект в слабой ссылке не станет null, что означает, что он был собран. На моей машине он живет около 1 секунды, потому что это интервал GC, с которого начинается моя JVM (это может меняться со временем в зависимости от использования памяти во время выполнения). По сути, мы удерживаем объект до следующего GC.

WeakHashMap

Это, вероятно, самый распространенный случай использования слабых ссылок. WeakHashMapэто особый тип сопоставления, который никогда не создает надежных ссылок для ключей. Это означает, что карта сама по себе не будет препятствовать сбору своих значений, что делает ее идеальной для недолговечных кэшей (как показано в приведенном выше коде). Мы полагаемся на GC для сбора объектов для нас, и нам не нужно беспокоиться об этом. По сути, если какой-то другой код использует значение из карты (через его ключ), он будет содержать сильную ссылку на него, что сделает значение неприемлемым для GC. Это позволяет нам создавать динамический кеш, полностью основанный на использовании. Вы должны признать, что это довольно аккуратно.

Другие варианты использования слабых ссылок

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

Мягкие ссылки

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

Этот простой фрагмент довольно быстро вызывает OOE, мы сохраняем сильные ссылки на большой массив байтов в списке. Так выглядит потребление памяти

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

Если мы начнем хранить мягкие ссылки в списке следующим образом:

Приложения внезапно начинают работать бесконечно. Вот как выглядит использование памяти

На этот раз GC очень агрессивен. Вы можете видеть, что каждый раз, когда используемая куча достигает максимального размера 4 ГБ, сборщик освобождает гигабайты оперативной памяти в виде мягких ссылок. И если мы начнем проверять список во время работы процесса, мы заметим, что он заполнен кучей SoftReferences, указывающих на null. Когда что-то доступно только через мягкую ссылку, мы говорим, что этот объект не является важным для приложения и его можно безопасно удалить, если память начинает заканчиваться. Надежные ссылки считаются важными, и в случае, если JVM больше не может их поддерживать, она выдает ошибку вместо того, чтобы продолжать работать в нестабильном состоянии, что может привести к несогласованности данных.

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

Фантомные ссылки

Фантомные ссылки существуют, поэтому мы можем отслеживать, когда объект собирается сборщиком мусора. Они не содержат никакой ссылки на объект (отсюда и название) и не мешают продолжительности его жизни (независимо от того, какую ссылку мы используем для доступа к нему). Это новый механизм финализации JVM, а предыдущий (метод финализации) теперь устарел и не должен использоваться.

Вот как мы можем их использовать

Точно так же мы создаем объект, а затем указываем на него PhantomReference. Однако мы также должны предоставить специальную ReferenceQueue. В строке 6 мы предлагаем сборщик мусора для JVM (и надеемся, что он выполнит наш запрос). pahntomReference.get() всегда дает нам null (у нас нет ссылки на объект, только его жизненный цикл). Код в строке 12 будет ожидать сбора объекта сборщиком мусора. Это может быть очень полезно, если у вас есть специальная логика завершения или вы хотите дождаться сбора одного огромного объекта, прежде чем создавать следующий.

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

дальнейшее чтение