Dlaczego metoda finalizacji jest zawarta w Javie?

44

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ą .

patstuart
źródło

Odpowiedzi:

41

Według Effective Java Joshua Blocha (wydanie drugie) istnieją dwa scenariusze, gdy finalize()jest to przydatne:

  1. 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

  2. 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.

Bbalchev
źródło
4
Z nr 2 brzmi jak natywny peer jest rodzimym zasobem, który wymaga siatki bezpieczeństwa i powinien mieć metodę zakończenia, a zatem nie powinien być osobnym punktem.
Mooing Duck
2
@MooingDuck: # 2 to osobny punkt, ponieważ jeśli natywny element równorzędny nie posiada zasobów krytycznych (np. Jest to obiekt wyłącznie w pamięci), nie ma potrzeby ujawniania jawnej metody zakończenia. Siatka bezpieczeństwa z nr 1 wyraźnie mówi o metodzie wypowiedzenia.
jhominal
55

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:

  1. Program wyłącza się szybko
  2. Obiekt „żyje wiecznie” w trakcie trwania programu
  3. Komputer wyłącza się / proces zostaje zabity przez system operacyjny / etc

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.

Ryan Cavanaugh
źródło
Doskonała odpowiedź. Miałem wrażenie, że nawet jeśli można to nazwać ... nadal powinno być uwzględnione. Przez chwilę byłem zdezorientowany pytaniem i połączonym na StackOverflow.
Poinformowano
3
Nie było tego w pytaniu, ale warto to omówić w kontekście „nie polegaj na wywołaniu finalizatora”: finalizator może zostać wywołany, ale tylko po arbitralnie długim opóźnieniu, po którym obiekt staje się nieosiągalny. Ma to znaczenie dla „rodzimych zasobów”, które są znacznie mniej dostępne niż pamięć (które są większością z nich, jak się okazuje).
26
„Finalizatory są ważne dla zarządzania rodzimymi zasobami”. - nooooooooo, przepraszam. Używanie finalizatorów do zarządzania rodzimymi zasobami to okropny pomysł (dlatego teraz mamy możliwość automatycznego zamykania i współpracy). GC dba tylko o pamięć, a nie o inne zasoby. Oznacza to, że możesz zabraknąć zasobów natywnych, ale wciąż masz wystarczającą ilość pamięci, aby nie wywołać GC - naprawdę tego nie chcemy. Również finalizatory powodują, że GC jest droższy, co też nie jest dobrym pomysłem.
Voo,
12
Zgadzam powinny nadal mieć wyraźny natywną strategii zarządzania zasobami innego niż finalizatorów, ale jeśli obiekt ma przeznaczyć je i nie zwalnia ich w finalizatora, ty otwierasz się na rodzimym przecieku w przypadku, gdy obiekt jest GC'd bez wyraźnego zwolnienia zasobu. Innymi słowy, finalizatory są ważną częścią natywnej strategii zarządzania zasobami, a nie rozwiązaniem problemu w ogóle.
Ryan Cavanaugh
1
@Voo prawdopodobnie bardziej poprawne jest stwierdzenie, że finalizator jest awarią w przypadku nieprzestrzegania kontraktu z obiektem ( close()nigdy nie jest wywoływany, zanim stanie się nieosiągalny)
maniak ratchet,
5

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!

komar
źródło
4

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.

supercat
źródło
3

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.

keshlam
źródło
1

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.

Daniel Pryden
źródło
5
Dlaczego podziwiam nadmierne uproszczenie problemu, „nie rób tego” nie jest odpowiedzią na „dlaczego on istnieje”.
Pierre Arlaud
1
Chyba nie napisałem tego dobrze w mojej odpowiedzi, ale (IMO) odpowiedź na pytanie „dlaczego to istnieje?” to „nie powinno”. W tych rzadkich przypadkach, gdy naprawdę potrzebujesz operacji finalizacji, prawdopodobnie chcesz ją zbudować samodzielnie PhantomReferencei ReferenceQueuezamiast niej.
Daniel Pryden
Miałem na myśli * oczywiście, chociaż nie głosowałem cię. Najbardziej pozytywna odpowiedź pokazuje jednak, jak użyteczna jest ta metoda, niejako unieważniając twój punkt.
Pierre Arlaud
1
Nawet w przypadku natywnych rówieśników uważam, że PhantomReferencejest 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ć.
Daniel Pryden
1
Niebezpieczeństwa związane z finalizatorami są opisane w C # w bardziej przystępny (zrozumiały dla programistów) sposób. , Jednak nie można sugerować, że mechanizmy leżące u podstaw języka Java i C # są takie same; w ich specyfikacjach nie ma nic takiego.
rwong
1

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.

Benni
źródło