Silne I Słabe Referencje W Java

Zostałem ostatnio zapytany przez pewną osobę co zrobić w przypadku gdy referencja do danego obiektu jest wg nas zbyt silna, tzn chcielibyśmy aby w pewnych przypadkach GC mógł usunąć obiekt, mimo iż wskazuje na niego pewna referencja. W “normalnym” zastosowaniu Javy tak jak to robi większość programistów łącznie ze mną, jest to po prostu niemożliwe - GC nie ma prawa usunąć obiektu, do którego istnieje jakakolwiek referencja. Z pomocą przychodzi jednak pakiet java.lang.ref, któremu postanowiłem się przyjrzeć nieco bliżej. W skład pakietu wchodzą następujące klasy:

  • Reference

  • SoftReference

  • WeakReference

  • PhantomReference

  • ReferenceQueue

Strong References (referencje silne)

Klasy SoftReference, WeakReference oraz PhantomReference reprezentują trzy dodatkowe typy referencji w Java. Standardowym typem referencji są referencje silne, które uzyskujemy po stworzeniu obiektu za pomocą operatora new (lub oczywiście poprzez skopiowanie referencji z jakiejś zmiennej) jak np.

1
List l = new LinkedList();

Wyrażenie to stworzy obiekt klasy LinkedList, do którego referencja zostanie zapisana w zmiennej “l”. Referencje tego typu to referencje silne. Jeżeli na dany obiekt nie wskazuje żadna referencja silna, GC jest zobligowany do usunięcia obiektu w trakcie jednej z przyszłych kolekcji.

SoftReference (referencje miękkie)

Klasa java.lang.ref.SoftReference reprezentuje referencję miękką. Referencje te różnią się od referencji silnych tym, że obiekty na które wskazują zostaną usunięte w sytuacji gdy GC stwierdzi niewystarczającą ilość pamięci dla innych obiektów aplikacji. Sprzątnięcie obiektów, na które wskazują referencje miękkie nastąpi jeszcze przed rzuceniem wyjątku OutOfMemoryError, co gwarantuje nam specyfikacja JVM. W praktyce oznacza to iż obiekty soft-rechable będą w pamięci tak długo jak długo będzie wolna pamięć dla innych obiektów. Poniżej przykład tworzenia miękkiej referencji do obiektu klasy User:

1
2
SoftReference sr = new SoftReference(new User("John", "Smith"));
User u = sr.get();

Dokładny moment usunięcia obiektu z pamięci niestety nie jest znany - można tylko przewidzieć kiedy to nastąpi. Mechanizm usuwania z pamięci miękkich referencji przedstawił swego czasu na swoim blogu Jeremy Manson. Wynika z niego, że decyzja o usunięci obiektu z pamięci jest podejmowana na podstawie dwóch czynników:

  1. ilości wolnej pamięci na stercie

  2. czasu stworzenia referencji

Po szczegóły podejmowania decyzji przez GC dotyczącej usuwania obiektów typu soft-reached odsyłam do bloga Jeremiego :)

Od siebie mogę tylko dodać, że momentem kiedy dość często będzie następowało usuwanie miękkich referencji jest tzw. warming-up aplikacji czyli sytuacja, w której następuje cykliczne zwiększanie rozmiaru sterty gdy parametr -Xms < -Xmx. Początkowy rozmiar sterty ustawiony jest na wartość -Xms a następnie cyklicznie zwiększany do rozmiaru -Xmx gdy aplikacja alokuje coraz więcej obiektów na stercie i trzyma do nich referencje. Co pewien czas następuje więc sytuacja niewystarczającego rozmiaru starcy co z kolei implikuje konieczność usunięcia referencji miękkich z pamięci. Sytuacji takiej można zaradzić ustawiając parametry -Xms i -Xmx na takie same wartości.

Jednym z zastosowań referencji miękkich są mechanizmy cache’owania danych. W implementacji takiego mechanizmu pożądane jest aby dane z cache zostały usunięte jeżeli zacznie brakować pamięci dla pozostałych obiektów. Referencje do obiektów mogą być oczywiście silne i sami możemy zarządzać sytuacjami wyjątkowymi, ale po co jeżeli pewne aspekty może załatwić za nas sam GC. Dla przykładu Ehcache, znana implementacja mechanizmu cache’owania w Java, używa właśnie referencji typu soft.

WeakReference (referencje słabe)

Referencje słabe są słabszą odmianą referencji miękkich. Oznacza to bardziej agresywną politykę GC w stosunku do nich - są one usuwane dużo częściej niż referencje miękkie, zazwyczaj podczas którejś kolekcji GC. Poniżej przykład tworzenia słabej referencji do obiektu klasy User:

1
2
WeakReference wr = new WeakReference(new User("John", "Smith"));
User u = wr.get();

Używając w kodzie referencji słabych należy się zabezpieczyć przed sytuacją kiedy metoda wr.get() za którymś razem może zwrócić po prostu null.

ReferenceQueue

W przypadku gdy SoftReference bądź WeakReference zaczną zwracać null obiekt, na który wskazywały jest oznaczony jako obiekt do finalizacji i usunięcia z pamięci. W takiej sytuacji chcielibyśmy jednak mieć możliwość wykonania pewnych operacji czyszczące związanych z daną referencją, np. w przypadku usunięcia referencji do pewnego klucza z WeakHashMap chcielibyśmy usunąć także wartość związaną z tym kluczem. Z pomocą przychodzą tzw. kolejki referencji, do których zapisywane są referencje do obiektów oznaczonych jako obiekty do finalizacji. Kolejki referencji wskazujemy w konstruktorach konkretnych klas referencji:

1
2
3
ReferenceQueue rq = new ReferenceQueue();
SoftReference sr = new SoftReference(new User("John", "Smith"), rq);
User u = sr.get();

PhantomReference

Referencje fantomowe działają trochę inaczej niż opisane wyżej referencje słabe i miękkie - zawsze zwracają null i są wykorzystywane tylko i wyłącznie do śledzenia ich pojawienia się w kolejce referencji co oznacza śmierć obiektu, na który wskazywały. Różnica dotyczy także momentu umieszczania obiektów w kolejce referencji. W przypadku WeakReference i SoftReference obiekty są umieszczane jeszcze przed ich finalizacją i usunięciem z pamięci. Nie jesteśmy więc w stanie wskazać momentu, kiedy obiekt całkowicie ginie. W przypadku PhantomReference, umieszczenie go w kolejce oznacza usunięcie obiektu z pamięci czyli jego śmierć.

Podsumowanie

Osobiście zachęcam do zapoznania się z typami referencji w Java, które pokrótce opisałem. Mam nadzieję, że wpis da sporą dawkę teorii, którą można będzie z powodzeniem wykorzystać w praktyce :)

Comments