W Javie przez większość czasu pracujemy ze zwykłymi, silnymi referencjami i nie musimy zastanawiać się nad tym, kiedy dokładnie obiekt zostanie usunięty z pamięci. Tym zajmuje się Garbage Collector. Są jednak sytuacje, w których warto mieć większą kontrolę nad tym, jak długo obiekt może pozostać osiągalny dla programu.
Do tego służą różne typy referencji dostępne w pakiecie java.lang.ref. Java udostępnia cztery podstawowe rodzaje referencji: silne, miękkie, słabe oraz fantomowe. Różnią się one tym, w jaki sposób wpływają na decyzję Garbage Collectora o usunięciu obiektu z pamięci.
Silna referencja
Silna referencja to domyślny typ referencji w Javie. Jeśli obiekt jest osiągalny przez silną referencję, Garbage Collector nie może go usunąć. Obiekt pozostanie w pamięci tak długo, jak długo istnieje przynajmniej jedna ścieżka silnych referencji prowadząca do niego.
To właśnie z takich referencji korzystamy na co dzień, przypisując obiekt do zmiennej, pola klasy albo elementu kolekcji.
StringBuilder builder = new StringBuilder("Kodomaniak");
// Obiekt jest osiągalny przez silną referencję,
// więc Garbage Collector go nie usunie.
System.out.println(builder.toString());
// Od tego momentu zmienna nie wskazuje już na obiekt.
// Jeśli nie ma innych referencji, obiekt może zostać usunięty przez GC.
builder = null;
Silne referencje są najprostsze i najczęściej używane. Problem pojawia się wtedy, gdy przez przypadek trzymamy je zbyt długo, na przykład w statycznej mapie lub cache’u bez mechanizmu czyszczenia. Wtedy obiekty nie mogą zostać zwolnione i może dojść do wycieku pamięci.
Miękka referencja
Miękka referencja (SoftReference) pozwala Garbage Collectorowi usunąć obiekt, ale zwykle dopiero wtedy, gdy JVM zaczyna potrzebować więcej pamięci. Taki obiekt jest więc słabiej chroniony niż przy silnej referencji, ale mocniej niż przy referencji słabej.
Klasycznym przykładem użycia miękkich referencji jest cache, którego zawartość może zostać odtworzona. Jeśli pamięci jest dużo, obiekty mogą zostać w cache’u. Jeśli pamięci zaczyna brakować, GC może je usunąć, zamiast doprowadzić do OutOfMemoryError.
import java.lang.ref.SoftReference;
public class SoftReferenceExample {
public static void main(String[] args) {
byte[] imageData = new byte[10 * 1024 * 1024]; // przykładowy ciężki obiekt
SoftReference<byte[]> cache = new SoftReference<>(imageData);
// Usuwamy silną referencję.
imageData = null;
byte[] fromCache = cache.get();
if (fromCache != null) {
System.out.println("Dane nadal są dostępne w cache'u");
} else {
System.out.println("Garbage Collector usunął dane z pamięci");
}
}
}
Warto jednak pamiętać, że współcześnie miękkie referencje nie zawsze są najlepszym wyborem do budowania cache’a. W praktycznych aplikacjach często lepiej użyć gotowych bibliotek, takich jak Caffeine, które oferują przewidywalne limity, wygasanie wpisów i lepszą kontrolę nad pamięcią.
Słaba referencja
Słaba referencja (WeakReference) nie chroni obiektu przed usunięciem przez Garbage Collector. Jeśli obiekt jest osiągalny wyłącznie przez słabe referencje, może zostać usunięty już przy najbliższym cyklu GC.
Ten mechanizm przydaje się wtedy, gdy chcemy powiązać dodatkowe informacje z obiektem, ale nie chcemy przez to przedłużać jego życia. Dobrym przykładem jest WeakHashMap, w której klucze są trzymane słabo. Gdy klucz nie jest już używany nigdzie indziej, wpis może zostać automatycznie usunięty z mapy.
import java.lang.ref.WeakReference;
public class WeakReferenceExample {
public static void main(String[] args) {
StringBuilder builder = new StringBuilder("Kodomaniak");
WeakReference<StringBuilder> weakReference = new WeakReference<>(builder);
System.out.println("Przed GC: " + weakReference.get());
// Usuwamy silną referencję.
builder = null;
// Tylko sugestia dla JVM — nie gwarantuje natychmiastowego działania GC.
System.gc();
if (weakReference.get() == null) {
System.out.println("Obiekt został usunięty");
} else {
System.out.println("Obiekt jeszcze istnieje");
}
}
}
Najważniejsze jest to, że wynik działania takiego programu może różnić się pomiędzy uruchomieniami. Wywołanie System.gc() jest tylko prośbą do JVM, a nie gwarancją natychmiastowego czyszczenia pamięci.
Referencja fantomowa
Referencja fantomowa (PhantomReference) jest najbardziej specyficzna z całej czwórki. Nie służy do ponownego pobierania obiektu, ponieważ metoda get() zawsze zwraca null. Jej głównym zastosowaniem jest wykrycie momentu, w którym obiekt został uznany za możliwy do usunięcia i trafił do kolejki referencji.
Do używania referencji fantomowych potrzebna jest ReferenceQueue. Dzięki niej możemy zareagować na fakt, że obiekt zakończył swój cykl życia z punktu widzenia GC. Ten mechanizm bywa wykorzystywany do sprzątania zasobów spoza sterty JVM, na przykład pamięci używanej poza JVM albo otwartych zasobów, takich jak pliki lub połączenia.
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
public class PhantomReferenceExample {
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<Resource> queue = new ReferenceQueue<>();
Resource resource = new Resource("plik.tmp");
PhantomReference<Resource> phantomReference =
new PhantomReference<>(resource, queue);
// PhantomReference nie pozwala pobrać obiektu.
System.out.println("phantomReference.get(): " + phantomReference.get());
// Usuwamy silną referencję.
resource = null;
System.gc();
Reference<? extends Resource> reference = queue.remove(1000);
if (reference != null) {
System.out.println("Obiekt trafił do kolejki referencji");
reference.clear();
} else {
System.out.println("Obiekt nie trafił jeszcze do kolejki");
}
}
static class Resource {
private final String name;
Resource(String name) {
this.name = name;
}
}
}
Referencje fantomowe są rzadko potrzebne w codziennym kodzie aplikacyjnym. Warto jednak wiedzieć, że istnieją, bo są ważnym elementem bardziej zaawansowanego zarządzania zasobami.
ReferenceQueue, czyli skąd wiadomo, że obiekt został zwolniony?
ReferenceQueue może być używana razem z miękkimi, słabymi i fantomowymi referencjami. Gdy GC zdecyduje, że obiekt powiązany z daną referencją może zostać usunięty, referencja może zostać dodana do kolejki. Program może taką kolejkę obserwować i wykonać dodatkowe sprzątanie.
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
public class ReferenceQueueExample {
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<Object> queue = new ReferenceQueue<>();
Object object = new Object();
WeakReference<Object> reference = new WeakReference<>(object, queue);
object = null;
System.gc();
Reference<?> removed = queue.remove(1000);
if (removed == reference) {
System.out.println("Referencja została dodana do kolejki");
}
}
}
Porównanie typów referencji
| Typ referencji | Czy chroni obiekt przed GC? | Typowe zastosowanie |
|---|---|---|
| Silna referencja | Tak | Standardowa praca z obiektami |
| Miękka referencja | Częściowo, zwykle dopóki w JVM jest wystarczająco dużo wolnej pamięci | Cache możliwy do odtworzenia |
| Słaba referencja | Nie | Mapy pomocnicze, metadane, WeakHashMap |
| Fantomowa referencja | Nie, a get() zwraca null | Zaawansowane sprzątanie zasobów po GC |
Podsumowanie
Referencje w Javie pozwalają lepiej zrozumieć, jak JVM decyduje o życiu obiektów w pamięci. W codziennym kodzie najczęściej korzystamy z silnych referencji, ale pozostałe typy są przydatne w konkretnych scenariuszach.
SoftReference może pomóc przy cache’u, WeakReference przy obiektach, które nie powinny być sztucznie utrzymywane przy życiu, a PhantomReference przy zaawansowanym sprzątaniu zasobów. Warto jednak używać ich świadomie, bo niewłaściwie zastosowane mogą bardziej utrudnić debugowanie niż rozwiązać problem.








