Не так давно я проводил собеседование с кандидатами, ищущими работу старшего инженера по разработке Java. Я часто беру у них интервью и говорю: «Можете ли вы рассказать мне о каких-нибудь слабых ссылках на Java?». Если интервьюер говорит: «Ну, это связано со сборкой мусора?», я в принципе буду доволен и не буду. Я не ожидаю, что ответом будет описание статьи, которая докапывается до сути вещей.
Однако, вопреки ожиданиям, я был удивлен, обнаружив, что среди почти 20 кандидатов со средним опытом разработки 5 лет и высокообразованным образованием только два человека знали о существовании слабых рекомендаций, но только один из этих двух человек действительно Узнайте об этом. В процессе собеседования я также пытался подсказать некоторые вещи, чтобы посмотреть, не скажет ли кто-нибудь вдруг «Вот и все», но результат оказался очень разочаровывающим. Я начал задаваться вопросом, почему эту часть знаний так игнорировали. В конце концов, слабые ссылки — очень полезная функция, и эта функция была введена, когда была выпущена Java 1.2 7 лет назад.
Что ж, я не ожидаю, что после прочтения этой статьи вы станете экспертом по слабым ссылкам, но думаю, вам следует хотя бы понимать, что такое слабые ссылки, как их использовать и в каких сценариях они используются. Поскольку это какие-то неизвестные понятия, я кратко объясню предыдущие три вопроса.
Сильная ссылка
Сильная ссылка — это ссылка, которую мы часто используем, и она записывается следующим образом:
Скопируйте код кода следующим образом:
Буфер StringBuffer = новый StringBuffer();
Вышеуказанное создает объект StringBuffer и сохраняет (сильную) ссылку на этот объект в буфере переменных. Да, это детская операция (простите меня за такие слова). Самое важное в сильной ссылке — это то, что она может сделать ссылку сильной, что определяет ее взаимодействие со сборщиком мусора. В частности, если объект доступен через строку строго ссылочных ссылок (полностью достижимый), он не будет переработан. Это именно то, что вам нужно, если вы не хотите, чтобы объект, с которым вы работаете, попал в переработку.
Но сильные цитаты настолько сильны
В программе довольно редко можно сделать класс нерасширяемым. Конечно, этого можно добиться, пометив класс как окончательный. Или может быть сложнее — вернуть интерфейс (Interface) через фабричный метод, содержащий неизвестное количество конкретных реализаций. Например, мы хотим использовать класс Widget, но этот класс не может быть унаследован, поэтому нельзя добавлять новые функции.
Но что нам делать, если мы хотим отслеживать дополнительную информацию об объекте «Виджет»? Предположим, нам нужно записать серийный номер каждого объекта, но поскольку класс Widget не содержит этого атрибута и не может быть расширен, мы не можем добавить этот атрибут. На самом деле, HashMap может полностью решить вышеуказанные проблемы.
Скопируйте код кода следующим образом:
серийныйNumberMap.put(виджет, виджетSerialNumber);
На первый взгляд это может показаться нормальным, но сильные ссылки на объекты виджетов могут вызвать проблемы. Мы можем быть уверены, что когда серийный номер виджета больше не понадобится, мы должны удалить запись с карты. Если мы не удалим его, это может вызвать утечку памяти или мы можем удалить используемые виджеты, когда удаляем их вручную, что может привести к потере действительных данных. На самом деле эти проблемы очень похожи. Это проблема, с которой часто сталкиваются языки без механизмов сборки мусора при управлении памятью. Но нам не нужно беспокоиться об этой проблеме, поскольку мы используем язык Java с механизмом сборки мусора.
Другая проблема, которую могут вызвать сильные ссылки, — это кэширование, особенно для больших файлов, таких как изображения. Предположим, у вас есть программа, которой необходимо обрабатывать изображения, предоставленные пользователями. Распространенным подходом является кэширование данных изображений, поскольку загрузка изображений с диска очень дорога, и в то же время мы также хотим избежать наличия двух копий одних и тех же данных изображения. в памяти одновременно.
Цель кэширования — предотвратить повторную загрузку ненужных файлов. Вы быстро обнаружите, что кеш всегда будет содержать ссылку на данные изображения в памяти. Использование сильных ссылок приведет к тому, что данные изображения останутся в памяти, что потребует от вас решить, когда данные изображения больше не нужны, и вручную удалить их из кэша, чтобы их мог вернуть сборщик мусора. Таким образом, вы в очередной раз вынуждены делать то, что делает сборщик мусора, и вручную решать, какие объекты очищать.
Слабая ссылка
Слабая ссылка — это просто ссылка, которая не настолько сильна для удержания объекта в памяти. Используя WeakReference, сборщик мусора поможет вам решить, когда указанный объект будет переработан, и удалить объект из памяти. Создайте слабую ссылку следующим образом:
Скопируйте код кода следующим образом:
eakReference<Widget> слабыйWidget = новый WeakReference<Widget>(виджет);
Вы можете получить настоящий объект виджета, используя слабый Widget.get(). Поскольку слабые ссылки не могут помешать сборщику мусора переработать их, вы обнаружите, что (при отсутствии сильной ссылки на объект виджета) при использовании внезапно возвращается значение null. получать.
Самый простой способ решить описанную выше проблему записи порядкового номера виджета — использовать встроенный в Java класс WeakHashMap. WeakHashMap — это почти то же самое, что и HashMap, с той лишь разницей, что на его ключи (а не значения!!!) ссылается WeakReference. Когда ключ WeakHashMap помечен как мусор, запись, соответствующая этому ключу, будет автоматически удалена. Это позволяет избежать описанной выше проблемы ручного удаления ненужных объектов виджета. WeakHashMap можно легко преобразовать в HashMap или Map.
Эталонная очередь
Как только объект слабой ссылки начинает возвращать значение null, объект, на который указывает слабая ссылка, помечается как мусор. И этот слабый ссылочный объект (а не тот объект, на который он указывает) бесполезен. Обычно в это время необходимо провести некоторую очистку. Например, WeakHashMap в это время удалит ненужные записи, чтобы избежать хранения бессмысленных слабых ссылок, которые растут бесконечно.
Очереди ссылок упрощают отслеживание нежелательных ссылок. Когда вы передаете объект ReferenceQueue при построении WeakReference, когда объект, на который указывает ссылка, помечен как мусор, ссылочный объект будет автоматически добавлен в очередь ссылок. Затем вы можете обрабатывать очередь входящих ссылок в течение фиксированного периода времени, например, выполнять некоторую работу по очистке от этих бесполезных ссылочных объектов.
Четыре вида ссылок
На самом деле в Java существует четыре типа ссылок разной силы: от сильных до слабых: сильные ссылки, мягкие ссылки, слабые ссылки и виртуальные ссылки. В приведенном выше разделе представлены сильные и слабые ссылки, а в следующем описаны оставшиеся две: мягкие ссылки и виртуальные ссылки.
Мягкая ссылка
Мягкая ссылка по сути аналогична слабой ссылке, за исключением того, что она обладает более сильной способностью предотвращать повторную обработку объекта, на который она указывает, в период сборки мусора, чем слабая ссылка. Если объект доступен по слабой ссылке, он будет уничтожен сборщиком мусора в следующем цикле сбора. Но если мягкая ссылка может быть достигнута, то объект останется в памяти на более длительное время. Сборщик мусора будет восстанавливать объекты, доступные по этим мягким ссылкам, только в том случае, если памяти недостаточно.
Поскольку объекты, доступные по мягким ссылкам, будут оставаться в памяти дольше, чем объекты, доступные по слабым ссылкам, мы можем использовать эту функцию для кэширования. Таким образом, вы можете сэкономить много вещей. Сборщик мусора будет заботиться о том, какой тип в данный момент доступен и степень потребления памяти для обработки.
Фантомная ссылка
В отличие от мягких и слабых ссылок, объекты, на которые указывают виртуальные ссылки, очень хрупкие, и мы не можем получить объекты, на которые они указывают, с помощью метода get. Его единственная функция заключается в том, что после повторного использования объекта, на который он указывает, он добавляется в очередь ссылок, чтобы зафиксировать, что объект, на который указывает ссылка, был уничтожен.
Когда объект, на который указывает слабая ссылка, становится доступным по слабой ссылке, слабая ссылка добавляется в очередь ссылок. Эта операция происходит до того, как фактически происходит уничтожение объекта или сборка мусора. Теоретически объект, который собирается переработать, можно воскресить с помощью несоответствующего метода деструктора. Но эта слабая ссылка будет уничтожена. Виртуальная ссылка добавляется в очередь ссылок только после того, как объект, на который она указывает, удаляется из памяти. Его метод get продолжает возвращать ноль, чтобы предотвратить возрождение почти уничтоженного объекта, на который он указывает.
Существует два основных сценария использования виртуальных ссылок. Это позволяет вам точно знать, когда объект, на который он ссылается, будет удален из памяти. И на самом деле это единственный способ в Java. Это особенно актуально при работе с большими файлами, такими как изображения. Когда вы определяете, что объект данных изображения должен быть переработан, вы можете использовать виртуальные ссылки, чтобы определить, переработан ли объект, прежде чем продолжить загрузку следующего изображения. Таким образом, вы сможете максимально избежать ужасных ошибок переполнения памяти.
Второй момент заключается в том, что виртуальные ссылки позволяют избежать многих проблем при уничтожении. Метод Finalize может воскресить объекты, которые вот-вот будут уничтожены, путем создания сильных ссылок, указывающих на них. Однако объект, который переопределяет метод Finalize, должен пройти два отдельных цикла сборки мусора, если он хочет быть переработанным. В первом цикле объект помечается как пригодный для вторичной переработки и затем может быть уничтожен. А потому, что еще есть небольшая вероятность того, что этот объект будет воскрешен в процессе разрушения. В этом случае сборщик мусора должен запуститься еще раз, прежде чем объект будет фактически уничтожен. Поскольку уничтожение может быть не очень своевременным, перед вызовом уничтожения объекта должно пройти неопределенное количество циклов сборки мусора. Это означает, что фактическая очистка объекта может произойти с большой задержкой. Вот почему вы по-прежнему получаете раздражающие ошибки нехватки памяти, когда большая часть кучи помечена как мусор.
Используя виртуальные ссылки, описанная выше ситуация будет решена. Когда виртуальная ссылка добавляется в очередь ссылок, у вас совершенно нет возможности получить уничтоженный объект. Потому что в это время объект был уничтожен из памяти. Поскольку виртуальную ссылку нельзя использовать для регенерации объекта, на который она указывает, ее объект будет очищен в первом цикле сборки мусора.
Очевидно, что метод Finalize не рекомендуется переопределять. Поскольку виртуальные ссылки, очевидно, безопасны и эффективны, удаление метода Finalize может существенно упростить виртуальную машину. Конечно, вы также можете переопределить этот метод, чтобы добиться большего. Все зависит от личного выбора.
Подвести итог
Я думаю, увидев это, многие люди начинают жаловаться. Почему вы говорите о старом API последних десяти лет? Ну, по моему опыту, многие Java-программисты не очень хорошо знают эти знания. Я думаю, что некоторые из них есть. - необходимо глубокое понимание, и я надеюсь, что каждый сможет что-то почерпнуть из этой статьи.