środa, sierpnia 30, 2017

finalize() - do czego służy, a do czego nie i z czym to się je.

Przesłuchując kandydatów zadaję im czasem pytanie o finalize.
Odkryłem dzięki temu, że to jedno pytanie może z dużą dokładnością wykryć czy ktoś zaczął programować w Java'ie przed czy po 2010 roku ;-)

Wiele osób, które zaczęły programować w Java'ie po 2010 roku twierdzi, że finalize służy do uruchomienia GC*, albo nawet że ta metoda uruchamia GC dla tego jednego obiektu**.

W rzeczywistości finalize jest wołana przez specjalny wątek Finalizera***, który sprawdza czy obiekt spełnia warunki do bycia zabitym, jeśli tak to woła metodę finalize obiektu po czym sam Finalizer zapomina o obiekcie i ginie.

Metoda finalize daje obiektowi szansę na posprzątanie po sobie.
Słowem klucz jest "szansę" ;-)

Nie ma gwarancji, że zostanie uruchomiona****, nie ma gwarancji, że w razie zostanie uruchomiona to będzie uruchomiona tym samym ClassLoaderem, który ładował klasę, gdy poleci jakikolwiek wyjątek to zostanie on zignorowany i nie trafi nawet do loga.

Jeśli gdzieś w kodzie masz finalize to traktuj to jako code smell.
Nie każde użycie jest złe, ale zawsze warto się przyjrzeć bo często okaże się, że użycie finalize nie było dobrym pomysłem.

Co ciekawe czasem ktoś używa finalize żeby sprzątać bo chce oszczędzić pamięci... a w rzeczywistości tej pamięci więcej zużywa ;-) bo JVM trzyma cały wielki obiekt Finalizer dla każdego obiektu, który nadpisał metodę finalize*****.

To ostatnie jest ciekawe. Metoda finalize może być zawołana TYLKO na obiekcie stworzonym z klasy, która nadpisała metodę finalize.
Jeśli klasa ma metodę z Object to nie ma szans by finalize zostało zawołane..
To samo tyczy się enumów.
Nawet jeśli ktoś napisze finalize() dla enuma to też nigdy nie zostanie zawołana.

Podsumowując.

  • metoda finalize() nie woła GC,
  • może zostać zawołana dla obiektu, który nie ma już żadnej referencji poza tą w Finalizerze******,
  • można jej w rzadkich przypadkach użyć do sprzątania po obiekcie (czyli do zwolnienia jakichś zasobów, w domyśle ciężkich)




* - do tego służy System.gc() i to nie jest uruchomienie, a co najwyżej sugestia dla GC, że chcielibyśmy by się uruchomił.
** - co byłoby dość karkołomne zważywszy, że musimy mieć referencję do obiektu by móc na nim zawołać jakaś metodę ;-).
*** - uproszczenie, tak naprawdę Finalizer jest tworzony dla każdego obiektu, który nadpisze metodę finalize(), a później jest jeszcze wątek, który dba o to by uruchamiać co jakiś czas metodę z Finalizera jeśli wychodzi mu, że istnieje tylko 1 referencja do obiektu (ale przyznam, że tego kodu nie oglądałem :-)).
**** - Runtime.runFinalizersOnExit(true) może to "zagwarantować", sęk w tym, że dla wszystkich obiektów, nawet tych, które są jeszcze używane w trakcie zamykania aplikacji.
***** - sam kiedyś w jednym projekcie miałem OOM na produkcji i w końcu doszedłem do tego, że to przez to, że masa krótko żyjących obiektów miała metodę finalize i większość pamięci zjadały nam Finalizery :-)
****** - no chyba, że ktoś zawoła Runtime.runFinalizersOnExit(true), wtedy jest szansa, że w trakcie wychodzenia z aplikacji Finalizer spróbuje zawołać finalize na obiekcie, który jest jeszcze gdzieś używany.


Podobne postybeta
JBoss rozrabiaka ;-)
Samochód jako zmniejszacz temperatury.... GC i jak to możliwe, że Young Generation może być zbyt duże, strzeż się finalize() i muzyczka :-) Czyli potok świadomości....
Referencje w Java'ie
Sztuczki tropiciela błędów - breakpoint na sterydach ;-)
Raport z emigracji ;-)

Brak komentarzy:

Prześlij komentarz