1. Обзор
В этой статье мы рассмотрим WeakHashMap
из пакета java.util
.
Чтобы понять структуру данных, мы воспользуемся ею здесь, чтобы развернуть простую реализацию кэша. Однако имейте в виду, что это предназначено для понимания того, как работает карта, и создание собственной реализации кэша почти всегда является плохой идеей.
Проще говоря, WeakHashMap
— это основанная на хеш-таблицах реализация интерфейса Map
с ключами типа WeakReference
.
Запись в WeakHashMap
будет автоматически удалена, когда ее ключ больше не используется в обычном режиме, а это означает, что нет ни одной ссылки
, указывающей на этот ключ. Когда процесс сборки мусора (GC) отбрасывает ключ, его запись фактически удаляется из карты, поэтому этот класс ведет себя несколько иначе, чем другие реализации Map
.
2. Сильные, мягкие и слабые ссылки
Чтобы понять, как работает WeakHashMap
, нам нужно взглянуть на класс WeakReference
— базовую конструкцию для ключей в реализации WeakHashMap
. В Java у нас есть три основных типа ссылок, которые мы объясним в следующих разделах.
2.1. Сильные ссылки
Сильная ссылка — это наиболее распространенный тип ссылки
, который мы используем в нашем повседневном программировании:
Integer prime = 1;
Переменная prime
имеет сильную ссылку
на объект Integer
со значением 1. Любой объект, у которого есть сильная ссылка, указывающая на него, не подходит для GC.
2.2. Мягкие ссылки
Проще говоря, объект, на который указывает SoftReference
, не будет собирать мусор до тех пор, пока JVM абсолютно не потребует памяти.
Давайте посмотрим, как мы можем создать SoftReference
в Java:
Integer prime = 1;
SoftReference<Integer> soft = new SoftReference<Integer>(prime);
prime = null;
Основной объект
имеет сильную ссылку, указывающую на него.
Затем мы превращаем основную
сильную ссылку в мягкую ссылку. После того, как эта сильная ссылка станет нулевой
, первичный
объект подходит для GC, но будет собираться только тогда, когда JVM абсолютно нуждается в памяти.
2.3. Слабые ссылки
Объекты, на которые ссылаются только слабые ссылки, с готовностью удаляются сборщиком мусора; в этом случае GC не будет ждать, пока ему понадобится память.
Мы можем создать WeakReference
в Java следующим образом:
Integer prime = 1;
WeakReference<Integer> soft = new WeakReference<Integer>(prime);
prime = null;
Когда мы сделали основную
ссылку null
, основной
объект будет удален сборщиком мусора в следующем цикле сборки мусора, так как на него нет другой строгой ссылки.
Ссылки типа WeakReference
используются в качестве ключей в WeakHashMap
.
3. WeakHashMap
как эффективный кэш памяти
Допустим, мы хотим создать кеш, который хранит большие объекты изображений в качестве значений и имена изображений в качестве ключей. Мы хотим выбрать правильную реализацию карты для решения этой проблемы.
Использование простого HashMap
не будет хорошим выбором, поскольку объекты-значения могут занимать много памяти. Более того, они никогда не будут извлечены из кэша процессом сборки мусора, даже если они больше не используются в нашем приложении.
В идеале нам нужна реализация карты
, которая позволяет сборщику мусора автоматически удалять неиспользуемые объекты. Когда ключ большого объекта изображения не используется в нашем приложении в каком-либо месте, эта запись будет удалена из памяти.
К счастью, WeakHashMap
обладает именно такими характеристиками. Давайте протестируем наш WeakHashMap
и посмотрим, как он себя ведет:
WeakHashMap<UniqueImageName, BigImage> map = new WeakHashMap<>();
BigImage bigImage = new BigImage("image_id");
UniqueImageName imageName = new UniqueImageName("name_of_big_image");
map.put(imageName, bigImage);
assertTrue(map.containsKey(imageName));
imageName = null;
System.gc();
await().atMost(10, TimeUnit.SECONDS).until(map::isEmpty);
Мы создаем экземпляр WeakHashMap
, в котором будут храниться наши объекты BigImage
. Мы помещаем объект BigImage
в качестве значения и ссылку на объект imageName
в качестве ключа. Имя изображения
будет храниться на карте как тип WeakReference
.
Затем мы устанавливаем для ссылки imageName значение
null
, поэтому больше нет ссылок, указывающих на объект bigImage
. Поведение WeakHashMap
по умолчанию заключается в том, чтобы восстановить запись, которая не имеет ссылки на нее, при следующем сборщике мусора, поэтому эта запись будет удалена из памяти следующим процессом сборки мусора.
Мы вызываем System.gc()
, чтобы заставить JVM запустить процесс GC. После цикла GC наша WeakHashMap
будет пустой:
WeakHashMap<UniqueImageName, BigImage> map = new WeakHashMap<>();
BigImage bigImageFirst = new BigImage("foo");
UniqueImageName imageNameFirst = new UniqueImageName("name_of_big_image");
BigImage bigImageSecond = new BigImage("foo_2");
UniqueImageName imageNameSecond = new UniqueImageName("name_of_big_image_2");
map.put(imageNameFirst, bigImageFirst);
map.put(imageNameSecond, bigImageSecond);
assertTrue(map.containsKey(imageNameFirst));
assertTrue(map.containsKey(imageNameSecond));
imageNameFirst = null;
System.gc();
await().atMost(10, TimeUnit.SECONDS)
.until(() -> map.size() == 1);
await().atMost(10, TimeUnit.SECONDS)
.until(() -> map.containsKey(imageNameSecond));
Обратите внимание, что только ссылка imageNameFirst имеет значение
null
. Ссылка imageNameSecond
остается неизменной. После запуска GC карта будет содержать только одну запись — imageNameSecond
.
4. Вывод
В этой статье мы рассмотрели типы ссылок в Java, чтобы полностью понять, как java.util.
WeakHashMap
работает. Мы создали простой кеш, который использует поведение WeakHashMap
, и проверили, работает ли он так, как мы ожидали.
Реализацию всех этих примеров и фрагментов кода можно найти в проекте GitHub, который является проектом Maven, поэтому его должно быть легко импортировать и запускать как есть.