Перейти к основному содержимому

Руководство по WeakHashMap в Java

· 4 мин. чтения

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, поэтому его должно быть легко импортировать и запускать как есть.