JLS Finalizacja i rodzaje referencji

Finalizacja

Służy do zwalniania zasobów zainicjalizowanych poza JVMem. Realizuje to samo co sekcja finally lub try with resources ale dla obiektów o widoczności większej niż zasięg metody. Umożliwia umieszczenie kodu zwalniającego zasoby wewnątrz obiektu którego dotyczy, co utrudnia niepoprawne wykorzystanie.

Klasa Object ma metodę protected void finalize() throws Throwable { }. Obiekty mogą nadpisywać tą metodę. Kontrakt tej metody mówi, że będzie wywołana przez Garbage Collectora przed ponownym użyciem pamięci zajmowanej przez obiekt (co może nie nastąpić). Czas jej wykonania wpływa na czas działania GC.
„Finalizer” może pojawiać się w dwóch kontekstach: jako implementacja metody finalize, lub jako obiekt rozszerzający FinalReference (patrz niżej) który przechowuje referencje do obiektu przeznaczonego do finalizacji.
Enumy nie mogą mieć finalizerów.

Mechanizm finalizacji jest w większości napisany w Javie. Wyjątek to pole zawierające elementy które mogą być kiedyś finalizowane (unfinalized) uzupełniane przez JVMa oraz metody natywne (według komentarzy w kodzie) pośredniczące w wywołaniu metod opisanych poniżej. Pośredniczące metody natywne równie dobrze mogą same zaimplementować te metody.

Metody System.runFinalization(), System.gc() w teorii mogą przyśpieszyć wywołanie finalizerów.
JVM nie czeka na skończenie pracy finalizerów kiedy się wyłącza. Wyjątek to zastosowanie System.runFinalizersOnExit(true) metoda jest niezalecana i ma dwie wady: finalizacja może być przeprowadzona na „żywym” obiekcie co może skutkować niespójnymi danymi oraz nie zapewnia, że finalizer zakończy pracę w sytuacji kiedy jego wykonanie rozpocznie się przed wyłączaniem JVM.
Po załadowaniu klasy Finalizer.java uruchamiany jest wątek który kolejno wykonuje finalizację obiektów dodanych do kolejki przez JVMa.

Model pamięci gwarantuje, że metoda będzie wykonywana po wszystkich zapisach a wszystkie odczyty nie będą widziały jej skutków. Finalizacja może przebiegać wielowątkowo. Aktualna javowa implementacja obejmuje dwa wątki dla finalizacji i jeden do obsługi referencji (patrz niżej). Główny obsługujący finalizacje ma priorytet o 2 mniejszy od maksymalnego. Drugi jest uruchamiany jako wątek systemowy i wykorzystywany (według dokumentacji) tylko dla runFinalization i runFinalizersOnExit. W przeciwieństwie do pierwszego nie wchodzi w stan uśpienia.
Finalizer może być wywołany tylko raz dla konkretnego obiektu (implementacja tego jest raczej po stronie JVMa).

W przeciwieństwie do konstruktora finalizer nie wywołuje swojego odpowiednika w nadklasie – trzeba to zrobić jawnie.
Jeśli klasa może być rozszerzana wykonanie finalizacji dla instancji klas rozszerzających można wymusić można przez utworzenie klasy wewnętrznej („strażnika”) zawierającego pożądaną implementacje finalize, a następnie przechowywania referencji do instancji tej klasy wewnątrz obiektu który może być rozszerzany. Czas życia „strażnika” będzie pokrywał się z czasem życia instancji klas rozszerzających.

Podział obiektów względem dostępności

  • reachable wykorzystywany przez aplikację
  • finalizer-reachable możliwy do uzyskania tylko przez obiekt przeznaczony do finalizacji
  • unreachable nieosiągalny

Optymalizacje kompilatora mogą sprawić, że obiekt będzie możliwy do finalizacji wcześniej niż to wynika z kodu. Dotyczy tylko referencji trzymanych na stosie (wewnątrz wywołań metody). Możliwa jest finalizacja obiektu na rzecz którego jeszcze wywoływane są metody o ile stan tego obiektu nie jest później odczytywany.

Finalizer umożliwa ponowne „ożywienie” obiektu (np. przez dodanie referencji do niego do listy z użyciem this). Taki obiekt tworzy wyciek pamięci bo GC traktuje go jako usunięty (uruchomiono jego finalizer) mimo, że do samego usunięcia nie doszło.

Jeśli podczas finalizacji będzie rzucony wyjątek to jest on ignorowany a finalizacja tego obiektu się kończy.


Typy referencji

Typy rozróżniane z punktu widzenia Garbage Collectora, nie w kategoriach obiektu na który wskazują (instancja klasy, interfejsu, tablicy lub nulla). GC bierze pod uwagę „najsilniejszy” typ referencji po którym można dotrzeć do obiektu. Wszystkie typy poza pierwszym to wrappery na silną referencje.

Dodatkowe typy referencji są umieszczone w pakiecie java.lang.ref. Umożliwiają rozdzielenie czasu życia obiektu od czasu życia referencji do niego, które domyślnie są jednakowe. Częsty przypadek wykorzystania to obiekty z metadanymi dotyczącymi innych obiektów. Metadane nie zawsze mogą być umieszczone wewnątrz obiektów które opisują. Jeśli zastosowano mapę do powiązania opisujących i opisywanych obiektów lub opisujące obiekty zawierają referencje do opisywanych wtedy istnieją trzy opcje:

  • czas życia opisywanych obiektów jest rozszerzany do czasu życia metadanych
  • czas życia opisywanych obiektów może być skracany przez jawne nullowanie referencji (czyli „ręczne” zarządzanie czasem życia obiektu)
  • wykorzystanie, poza zwykłymi, także referencji nie wpływających na działanie GC.

strong

Standardowe referencje, możliwość dotarcia do obiektu (zaczynając w GC roots) przez tą referencję uniemożliwia usunięcie go przez GC.

soft

Referencja gwarantuje usunięcie obiektu zanim JVM rzuci OutOfMemoryError. Nie ma innych gwarancji co do czasu życia obiektu: może zostać usunięty od razu po utworzeniu lub nigdy (jeśli nie brakuje pamięci). Implementacje JVMa są zachęcane do nieusuwania obiektów które były niedawno wykorzystywane. Przeważnie wykorzystywane przy implementowaniu cache’a.

weak

Istnienie odwołania przez słabą referencje nie wpływa na czas życia obiektu. Stosowane np. do implementacji mapowań standaryzująych. Przykładem takiego mapowania jest cache Integera czy BigDecimala (mimo że oba używają silnych referencji – mapowane obiekty są małe). Mapa agreguje wszystkie obiekty ale nie zapobiega ich usunięciu. Dopiero użytkownik mapy tworzy silną referencje do obiektu na którym pracuje uniemożliwiając jego usunięcie.

phantom

Jedyny typ referencji który nie umożliwia „dotarcia” do wskazywanego elementu. Używany, w połączeniu z kolejką referencji, do informowania o tym, że obiekt może zostać usunięty (finalizacja już miała miejsce). Analogicznie działanie umożliwia finalizer obiektu ale użycie tego typu referencji jest bezpieczniejsze, przez brak możliwości „wskrzeszenia” obiektu. Użycie phantom reference umożliwia kontrolowanie który wątek odpowiada za finalizacje, w przypadku finallize aplikacja nie ma na to wpływu. W przeciwieństwie do finallize kod zwalniający zasoby jest umieszczony poza obiektem którego dotyczy.

final

Wykorzystywany przez finalizery. GC pośrednio tworzy ich instancje.


Reference Queue

Referencje inne niż silne można powiązać z kolejką. Kolejka dostarcza informację, o tym że zmieniła się dostępność referenta. Obiekty referencji Soft i Weak trafiają do kolejki w momencie w którym wskazywane obiekty przestają być możliwe do otrzymania przez te typy referencji (czyli w momencie opuszczania stanu Soft Reachable i Weak Reachable). Odwrotnie dla Phantom Reference: obiekty trafiają do kolejki w momencie w którym wchodzą w stan Phantom Reachable. Final Reference są tworzone przez GC kiedy obiekt nadaje się do finalizacji.
Obiekty referencji same podlegają usuwaniu przez GC. Jeśli wskazywany obiekt został usunięty wrapper zwraca nulla.

Jeśli referencje Soft i Weak zostaną usunięte przez GC wtedy automatycznie trafiają do kolejki o ile są do jakiejś przypisane.
Obiekt do którego istnieje Phantom Reference nie zostanie usunięty do czasu aż jego wszystkie fantomowe referencje nie zostaną usunięte lub znullowane przez metodę clear().

Reference Handler

Po załadowaniu klasy dziedziczącej po Reference tworzony jest oddzielny wątek daemona ReferenceHandler, z najwyższym priorytetem, którego zadanie to dodawanie referencji do kolejek. Referencje są dodawane do statycznego pola transient private Reference discovered; w klasie Reference przez Garbage Collectora, pole jest synchronizowane i umożliwia połączenie obiektów referencji w listę. GC rozszerza tę listę a Reference Handler ją skraca.

WeakHashMap

Implementacja mapy która wykorzystuje słabe referencje do przechowywania kluczy. W momencie usunięcia klucza jest on dodawany do wewnętrznej kolejki referencji. Następna interakcja z mapą usunie wpis który zawierał usunięty klucz. Trzeba upewnić się, że wartość nie ma referencji do swojego klucza (zapobiegłoby to jego usunięciu). Wartości mogą być ręcznie opakowane przez słabą referencję przed umieszczeniem w mapie, ale w przypadku ich finalizacji, odpowiadające im wpisy nie będą automatycznie usuwane.


Cleaner

Jeśli kod finalizujący jest prosty może być umieszczony w Cleanerze. Cleaner jest elementem dwukierunkowej listy, zawiera referenta (jako fantomową referencję), referencje do następnego i poprzedniego Cleanera oraz kod wywoływany w czasie sprzątania (w formie Runnable). Sprzątanie jest wykonywane dla wszystkich elementów listy. Kod sprzątający jest wykonywany przez wątek ReferenceHandler a nie wątek/wątki finalizujące. Istnienie Cleaner nie wyłącza finalizera. Cleaner znajduje się w paczce sun.misc co sugeruje niewykorzystywanie go poza core’owym kodem javy. Jest wykorzystywany w pakiecie NIO

Java 9 wprowadziła nowszą wersje Cleanera umieszczoną w java.lang.ref razem powyższymi typami referencji.

Źródła:
„The Java® Language Specification Java SE 8 Edition” James Gosling, Bill Joy, Guy Steele, Gilad Bracha, Alex Buckley
„Inside the Java Virtual Machine” Bill Venners
„Java. Efektywne programowanie” Wydanie II Joshua Bloch
Plugging memory leaks with soft references Brian Goetz
Plugging memory leaks with weak references Brian Goetz
Dokumentacja Soft Reference
Dokumentacja Weak Reference
Dokumentacja Phantom Reference
Dokumentacja Reference – klasy bazowej dla powyższych
Dokumentacja Reference Queue
Dokumentacja WeakHashMap
Kod Cleanera