Zgodnie z tym postem nigdy nie powinniśmy polegać na nazwie metody finalizacji. Dlaczego więc Java w ogóle umieściła go w języku programowania?
Wydaje się, że włączenie jakiejkolwiek funkcji, która może zostać wywołana, jest straszną decyzją .
Zgodnie z tym postem nigdy nie powinniśmy polegać na nazwie metody finalizacji. Dlaczego więc Java w ogóle umieściła go w języku programowania?
Wydaje się, że włączenie jakiejkolwiek funkcji, która może zostać wywołana, jest straszną decyzją .
Odpowiedzi:
Według Effective Java Joshua Blocha (wydanie drugie) istnieją dwa scenariusze, gdy finalize()
jest to przydatne:
Jednym z nich jest działanie jako „siatka bezpieczeństwa” na wypadek, gdyby właściciel obiektu zapomniał wywołać metodę wyraźnego zakończenia. Chociaż nie ma gwarancji, że finalizator zostanie wywołany niezwłocznie, może być lepiej zwolnić zasób później niż nigdy, w tych (miejmy nadzieję rzadkich) przypadkach, gdy klient nie wywoła metody jawnego zakończenia. Ale finalizator powinien zapisać ostrzeżenie, jeśli stwierdzi, że zasób nie został zakończony
Drugie uzasadnione użycie finalizatorów dotyczy obiektów z rodzimymi obiektami równorzędnymi. Natywny element równorzędny jest rodzimym obiektem, do którego normalny obiekt deleguje za pomocą metod macierzystych. Ponieważ natywny element równorzędny nie jest normalnym obiektem, śmieciarz nie wie o nim i nie może go odzyskać po odzyskaniu elementu równorzędnego Java. Finalizator jest odpowiednim narzędziem do wykonania tego zadania, zakładając, że natywny peer nie ma żadnych krytycznych zasobów. Jeśli natywny peer posiada zasoby, które muszą zostać niezwłocznie zakończone, klasa powinna mieć jawną metodę zakończenia, jak opisano powyżej. Metoda zakańczania powinna robić wszystko, co jest wymagane do uwolnienia zasobu krytycznego.
Więcej informacji można znaleźć w punkcie 7, strona 27.
Finalizatory są ważne dla zarządzania rodzimymi zasobami. Na przykład Twój obiekt może wymagać przydzielenia WidgetHandle z systemu operacyjnego przy użyciu interfejsu API innego niż Java. Jeśli nie zwolnisz tego WidgetHandle, gdy twój obiekt jest GC'd, będziesz przeciekać WidgetHandles.
Ważne jest to, że przypadki „nigdy nie nazywa się finalizatora” rozkładają się po prostu:
We wszystkich tych trzech przypadkach albo nie masz rodzimego przecieku (z powodu tego, że twój program już nie działa), albo masz już obcy wyciek (jeśli nadal alokujesz zarządzane obiekty bez nich GC'd).
Ostrzeżenie „nie polegaj na wywołaniu finalizatora” tak naprawdę dotyczy tego, aby nie używać finalizatorów do logiki programu. Na przykład nie chcesz śledzić, ile obiektów istnieje we wszystkich instancjach programu, zwiększając licznik w pliku podczas budowy i zmniejszając go w finalizatorze - ponieważ nie ma gwarancji, że twoje obiekty będą sfinalizowany, ten licznik plików prawdopodobnie nigdy nie wróci do 0. Jest to naprawdę szczególny przypadek bardziej ogólnej zasady, że nie powinieneś polegać na normalnym kończeniu programu (awarie zasilania itp.).
Jednak w przypadku zarządzania rodzimymi zasobami przypadki, w których finalizator nie działa, odpowiadają przypadkom, w których nie ma znaczenia, czy nie działa.
close()
nigdy nie jest wywoływany, zanim stanie się nieosiągalny)
Cel tej metody wyjaśniono w dokumentacji API w następujący sposób:
jest wywoływane, jeśli i kiedy wirtualna maszyna Java ustali, że nie ma już żadnych środków, za pomocą których można uzyskać dostęp do tego obiektu za pomocą dowolnego wątku, który jeszcze nie zmarł, z wyjątkiem działań podjętych w wyniku sfinalizowania jakiegoś innego obiektu lub klasa, która jest gotowa do sfinalizowania ...
zwykłym celem
finalize
... jest wykonywanie operacji czyszczenia, zanim obiekt zostanie nieodwołalnie odrzucony . Na przykład metoda finalizacji dla obiektu reprezentującego połączenie wejścia / wyjścia może wykonywać jawne transakcje we / wy, aby przerwać połączenie, zanim obiekt zostanie trwale odrzucony ...
Jeśli dodatkowo interesują Cię powody, dla których projektanci języków wybrali, że „obiekt jest nieodwołalnie odrzucony” ( wyrzucanie elementów bezużytecznych ) w sposób niezależny od programisty aplikacji („nigdy nie powinniśmy polegać”), wyjaśniono to w odpowiedzi na powiązane pytanie :
automatyczne wyrzucanie elementów bezużytecznych ... eliminuje całe klasy błędów programistycznych popełnianych przez programistów C i C ++. Możesz napisać kod Java z pewnością, że system szybko wykryje wiele błędów i że główne problemy nie staną w stanie uśpienia, dopóki nie zostanie dostarczony kod produkcyjny.
Powyższy cytat z kolei został zaczerpnięty z oficjalnej dokumentacji na temat celów projektowych Java , to znaczy można je uznać za autorytatywne odniesienie wyjaśniające, dlaczego projektanci języków Java zdecydowali w ten sposób.
Bardziej szczegółową i niezależną od języka dyskusję na temat tych preferencji można znaleźć w sekcji 9.6 OOSC Automatyczne zarządzanie pamięcią (w rzeczywistości nie tylko ta sekcja, ale cały rozdział 9 jest bardzo wart przeczytania, jeśli interesują Cię takie rzeczy). Ta sekcja otwiera się jednoznacznym stwierdzeniem:
Dobre środowisko OO powinno oferować mechanizm automatycznego zarządzania pamięcią, który wykrywa i odzyskuje nieosiągalne obiekty, umożliwiając twórcom aplikacji skoncentrowanie się na tworzeniu aplikacji.
Poprzednia dyskusja powinna wystarczyć, aby pokazać, jak ważne jest, aby mieć dostęp do takiego ułatwienia. Według słów Michaela Schweitzera i Lamberta Strethera:
Program zorientowany obiektowo bez automatycznego zarządzania pamięcią jest mniej więcej taki sam jak szybkowar bez zaworu bezpieczeństwa: prędzej czy później coś na pewno wybuchnie!
Finalizatory istnieją, ponieważ oczekiwano, że będą one skutecznym sposobem zapewnienia, że rzeczy zostaną wyczyszczone (nawet jeśli w praktyce tak nie jest), a ponieważ kiedy zostały wymyślone, lepsze sposoby zapewnienia czyszczenia (takie jak fantomy referencje i try-with -resources) jeszcze nie istniało. Z perspektywy czasu Java byłaby prawdopodobnie lepsza, gdyby wysiłek włożony w wdrożenie jej funkcji „finalizacji” został poświęcony na inne sposoby oczyszczania, ale nie było to jasne w czasie, gdy Java była początkowo rozwijana.
CAVEAT: Być może jestem nieaktualny, ale rozumiem to kilka lat temu:
Ogólnie rzecz biorąc, nie ma gwarancji, że uruchomi się finalizator - a nawet że w ogóle będzie działał, chociaż niektóre maszyny JVM pozwalają zażądać pełnej GC i finalizacji przed zakończeniem programu (co oczywiście oznacza, że program trwa dłużej wyjść, co nie jest domyślnym trybem działania).
Wiadomo też, że niektóre GC wyraźnie opóźniają lub unikają obiektów GC, które miały finalizatory, w nadziei, że poprawi to wydajność testów porównawczych.
Te zachowania niestety są sprzeczne z pierwotnymi powodami, dla których zalecono finalizatory i zachęciły do korzystania z jawnie nazywanych metod zamykania systemu.
Jeśli masz obiekt, który naprawdę musi zostać oczyszczony przed jego odrzuceniem, i jeśli naprawdę nie możesz ufać użytkownikom, że to zrobi, warto rozważyć finalizator. Ale ogólnie istnieją dobre powody, dla których nie widzisz ich tak często we współczesnym kodzie Java, jak w niektórych wczesnych przykładach.
Google Java Style Guide ma jakieś rady mędrca na ten temat:
Jest to niezwykle rzadkie , aby zastąpić
Object.finalize
.Wskazówka : nie rób tego. Jeśli absolutnie musisz, najpierw bardzo uważnie przeczytaj i zrozum Effective Java Item 7, „Unikaj finalizatorów”, a następnie nie rób tego.
PhantomReference
i ReferenceQueue
zamiast niej.
PhantomReference
jest to lepsze rozwiązanie. Finalizatory to brodawka pozostawiona z wczesnych dni Javy, a Object.clone()
typy podobne i surowe są częścią języka, o którym najlepiej zapomnieć.
Java Language Specification (Java SE 7) stanowi:
Finalizatory dają szansę na zwolnienie zasobów, których automatyczny menedżer pamięci nie może uwolnić automatycznie. W takich sytuacjach samo odzyskanie pamięci używanej przez obiekt nie gwarantowałoby odzyskania posiadanych zasobów.