>>
Ключевое различие: стратегия сборки мусора
Разница между SoftReference и WeakReference заключается в том, когда сборщик мусора (GC) удаляет объект, на который указывает ссылка. Оба типа ссылок позволяют объекту быть собранным, но условия для этого разные.
WeakReference — немедленная утилизация
Объект, доступный только через WeakReference, будет удалён при ближайшем проходе GC (когда GC обнаружит, что на объект нет сильных ссылок). Пример:
import java.lang.ref.WeakReference;
public class WeakExample {
public static void main(String[] args) {
Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<>(obj);
obj = null; // удаляем сильную ссылку
System.gc(); // просим GC
// Скорее всего, weakRef.get() вернёт null
System.out.println(weakRef.get()); // null
}
}
SoftReference — отложенная утилизация
Объект через SoftReference будет удалён только перед тем, как JVM выбросит OutOfMemoryError. GC оставляет такие объекты в живых до тех пор, пока памяти достаточно. Пример:
import java.lang.ref.SoftReference;
public class SoftExample {
public static void main(String[] args) {
Object obj = new Object();
SoftReference<Object> softRef = new SoftReference<>(obj);
obj = null;
System.gc();
// Скорее всего, softRef.get() вернёт объект (памяти много)
System.out.println(softRef.get()); // не null
}
}
Почему ваш код ведёт себя нестабильно?
Вызов System.gc() — это лишь рекомендация JVM запустить GC. JVM может проигнорировать её. Даже если GC запустится, он может не удалить WeakReference сразу, если в текущем цикле не проверяются слабые ссылки. А SoftReference может быть удалён, только если JVM решит, что памяти критически мало. В вашем примере памяти много, поэтому softRef почти всегда жив, а weakRef может быть жив или мёртв в зависимости от реализации GC.
Практические сценарии
- WeakReference — для кэшей, которые не должны мешать сборке мусора (например,
WeakHashMap). Идеально для метаданных, которые можно пересоздать.
- SoftReference — для кэшей больших объектов (изображения, загруженные файлы). Пока памяти достаточно — кэш работает, при нехватке — объекты удаляются.
Пример реального использования SoftReference для кэша
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
public class ImageCache {
private Map<String, SoftReference<byte[]>> cache = new HashMap<>();
public byte[] getImage(String key) {
SoftReference<byte[]> ref = cache.get(key);
if (ref != null) {
byte[] image = ref.get();
if (image != null) return image;
}
// Загружаем изображение (например, из файла)
byte[] newImage = loadFromDisk(key);
cache.put(key, new SoftReference<>(newImage));
return newImage;
}
private byte[] loadFromDisk(String key) {
// заглушка
return new byte[1024];
}
}
Важное предостережение
Не полагайтесь на SoftReference для критически важных данных — GC может удалить их в любой момент при нехватке памяти. Всегда проверяйте ref.get() на null.
Итог: WeakReference = "собери, как только сможешь", SoftReference = "собери, только если очень надо".
<<>>
Дополнение: PhantomReference и ReferenceQueue
Коллеги верно описали разницу, но стоит упомянуть третий тип — PhantomReference, и механизм ReferenceQueue, который часто используется вместе со слабыми и мягкими ссылками.
PhantomReference — это ссылка, объект по которой никогда не получить через get() (всегда возвращает null). Она используется для отслеживания момента, когда объект реально удалён из памяти. Это полезно для освобождения внешних ресурсов (например, закрытие файлов).
ReferenceQueue — это очередь, в которую помещаются ссылки после того, как объект, на который они указывали, был собран GC. Это позволяет выполнить очистку асинхронно. Пример:
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
public class CleanupExample {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
ReferenceQueue<Object> queue = new ReferenceQueue<>();
WeakReference<Object> weakRef = new WeakReference<>(obj, queue);
obj = null;
System.gc();
// Ожидаем, пока ссылка попадёт в очередь
WeakReference<?> ref = (WeakReference<?>) queue.remove(1000);
if (ref != null) {
System.out.println("Объект собран! Выполняем очистку...");
// Здесь можно освободить ресурсы
}
}
}
Этот подход часто используется в библиотеках для автоматического управления памятью — например, в реализации WeakHashMap или в кэшах с автоматической очисткой.