Poszukuję wglądu w decyzje dotyczące projektowania języka w zbieraniu śmieci. Może ekspert językowy mógłby mnie oświecić? Pochodzę z języka C ++, więc ten obszar jest dla mnie zaskakujący.
Wydaje się, że prawie wszystkie współczesne języki odśmiecania z obsługą obiektów OOPy, takie jak Ruby, JavaScript / ES6 / ES7, Actionscript, Lua itp. Całkowicie pomijają paradygmat destruktora / finalizacji. Python wydaje się być jedynym z tą class __del__()
metodą. Dlaczego to? Czy istnieją ograniczenia funkcjonalne / teoretyczne w obrębie języków z automatycznym zbieraniem śmieci, które uniemożliwiają skuteczne wdrożenie metody destruktor / finalizacja obiektów?
Bardzo brakuje mi, aby te języki traktowały pamięć jako jedyny zasób warty zarządzania. Co z gniazdami, uchwytami plików, stanami aplikacji? Bez możliwości zaimplementowania niestandardowej logiki do czyszczenia zasobów innych niż pamięć i stanów przy finalizacji obiektu, muszę zaśmiecić moją aplikację myObject.destroy()
wywołaniami stylu niestandardowego , umieszczając logikę czyszczenia poza moją „klasą”, przerywając próbę enkapsulacji i relegując moją aplikacja do wycieków zasobów z powodu błędu ludzkiego, a nie automatycznie obsługiwana przez gc.
Jakie decyzje dotyczące projektowania języka prowadzą do braku możliwości wykonania przez te języki niestandardowej logiki przy usuwaniu obiektów? Muszę sobie wyobrazić, że istnieje dobry powód. Chciałbym lepiej zrozumieć techniczne i teoretyczne decyzje, które spowodowały, że te języki nie miały wsparcia niszczenia / finalizacji obiektów.
Aktualizacja:
Być może lepszy sposób sformułowania mojego pytania:
Dlaczego język miałby wbudowaną koncepcję instancji obiektów z klasami lub strukturami podobnymi do klasy wraz z niestandardową instancją (konstruktorami), a jednocześnie całkowicie pomijałby funkcję niszczenia / finalizowania? Języki, które oferują automatyczne zbieranie śmieci, wydają się być głównymi kandydatami do wspierania niszczenia / finalizacji obiektów, ponieważ wiedzą ze 100% pewnością, że obiekt nie jest już używany. Jednak większość tych języków go nie obsługuje.
Nie sądzę, że jest to przypadek, w którym destruktor może nigdy nie zostać wywołany, ponieważ byłby to wyciek pamięci rdzenia, którego gcs mają uniknąć. Widziałem możliwy argument, że destruktor / finalizator może nie zostać wywołany do pewnego nieokreślonego czasu w przyszłości, ale to nie powstrzymało Java ani Pythona od obsługi tej funkcji.
Jakie są główne powody projektowania języka, które nie obsługują żadnej formy finalizacji obiektu?
finalize
/destroy
jest kłamstwem? Nie ma gwarancji, że kiedykolwiek zostanie wykonany. I nawet jeśli nie wiesz, kiedy (biorąc pod uwagę automatyczne wyrzucanie elementów bezużytecznych), a jeśli to konieczne, kontekst nadal istnieje (być może został już zebrany). Bezpieczniej jest więc zapewnić spójny stan na inne sposoby i można zmusić programistę do zrobienia tego.Odpowiedzi:
Wzorzec, o którym mówisz, w którym obiekty wiedzą, jak oczyścić swoje zasoby, należy do trzech odpowiednich kategorii. Nie łączmy destruktorów z finalizatorami - tylko jeden jest powiązany z odśmiecaniem:
Wzór finalizator : metoda oczyszczania uznane automatycznie, zdefiniowane przez programistę, nazywa się automatycznie.
Finalizatory są wywoływane automatycznie przed zwolnieniem przez moduł odśmiecający. Termin ma zastosowanie, jeśli zastosowany algorytm odśmiecania może określić cykle życia obiektu.
Wzór destructor : metoda oczyszczania uznane automatycznie, zdefiniowane przez programistę, zwany automatycznie tylko czasami.
Destruktory mogą być wywoływane automatycznie dla obiektów alokowanych na stosie (ponieważ czas życia obiektu jest deterministyczny), ale muszą być jawnie wywoływane na wszystkich możliwych ścieżkach wykonywania dla obiektów alokowanych na stercie (ponieważ czas życia obiektu nie jest deterministyczny).
Wzór dysponentem : metoda oczyszczania oświadczył zdefiniowane i nazywany przez programistę.
Programiści opracowują metodę usuwania i nazywają ją sami - tutaj
myObject.destroy()
spada twoja metoda niestandardowa . Jeśli usuwanie jest absolutnie wymagane, należy wezwać dyspozytorów na wszystkich możliwych ścieżkach wykonania.Finalizatory to droidy, których szukasz.
Wzorzec finalizatora (wzorzec, o który pyta pytanie) to mechanizm kojarzenia obiektów z zasobami systemowymi (gniazdami, deskryptorami plików itp.) W celu wzajemnego odzyskiwania przez moduł wyrzucający elementy bezużyteczne. Jednak finalizatory są zasadniczo zdane na użytek algorytmu wyrzucania elementów bezużytecznych.
Rozważ to swoje założenie:
Technicznie fałszywe (dziękuję, @babou). Śmieciowanie zasadniczo dotyczy pamięci, a nie obiektów. To, czy algorytm gromadzenia danych uświadamia sobie, że pamięć obiektu nie jest już używana, zależy od algorytmu i (ewentualnie) od tego, w jaki sposób obiekty się do siebie odnoszą. Porozmawiajmy o dwóch typach śmieciarek wykonawczych. Istnieje wiele sposobów na zmianę i rozszerzenie podstawowych technik:
Śledzenie GC. Te pamięć śledzenia, a nie obiekty. O ile nie są do tego ulepszone, nie zachowują referencji do obiektów z pamięci. O ile nie zostaną rozszerzone, te GC nie będą wiedziały, kiedy obiekt może zostać sfinalizowany, nawet jeśli wiedzą, kiedy jego pamięć jest nieosiągalna. Dlatego połączenia z finalizatorem nie są gwarantowane.
Liczenie referencyjne GC . Wykorzystują one obiekty do śledzenia pamięci. Modelują osiągalność obiektu za pomocą ukierunkowanego wykresu odniesień. Jeśli na wykresie odniesienia do obiektu znajduje się cykl, wówczas do wszystkich obiektów w cyklu nigdy nie zostanie wywołany finalizator (oczywiście do zakończenia programu). Ponownie połączenia z finalizatorem nie są gwarantowane.
TLDR
Wywóz śmieci jest trudny i różnorodny. Nie można zagwarantować połączenia z finalizatorem przed zakończeniem programu.
źródło
finalize()
spowoduje ponowne odwołanie do czyszczonego obiektu?). Jednak niemożność zagwarantowania, że finalizator zostanie wywołany przed zakończeniem programu, nie powstrzymała Java od jego obsługi. Nie mówię, że twoja odpowiedź jest niepoprawna, a może niepełna. Wciąż bardzo dobry post. Dziękuję Ci.W skrócie
Finalizacja nie jest prostą sprawą, którą zajmują się śmieciarze. Jest łatwy w użyciu z GC do zliczania referencji, ale ta rodzina GC jest często niekompletna, wymagając kompensacji wycieków pamięci przez jawne wywołanie zniszczenia i finalizacji niektórych obiektów i struktur. Śledzenie śmieciarek jest znacznie bardziej skuteczne, ale znacznie trudniej jest zidentyfikować obiekt, który ma zostać sfinalizowany i zniszczony, a nie tylko identyfikację nieużywanej pamięci, co wymaga bardziej złożonego zarządzania, z kosztem czasu i przestrzeni oraz złożoności implementacja.
Wprowadzenie
Zakładam, że pytasz, dlaczego języki odśmiecania śmieci nie obsługują automatycznie zniszczenia / finalizacji w procesie odśmiecania, jak wskazano w uwadze:
Nie zgadzam się z zaakceptowaną odpowiedzią udzieloną przez kdbanman . Chociaż stwierdzone fakty są w większości poprawne, choć silnie stronnicze w stosunku do liczenia referencji, nie sądzę, aby właściwie wyjaśniały sytuację, na którą narzekały pytania.
Nie wierzę, że terminologia opracowana w tej odpowiedzi stanowi poważny problem i jest bardziej prawdopodobne, że coś się pomyli. Rzeczywiście, jak przedstawiono, terminologia zależy głównie od sposobu aktywacji procedur, a nie od tego, co robią. Chodzi o to, że we wszystkich przypadkach istnieje potrzeba sfinalizowania obiektu, który nie jest już potrzebny, wraz z jakimś procesem czyszczenia i uwolnienia wszystkich wykorzystywanych zasobów, a pamięć jest tylko jednym z nich. Najlepiej byłoby, gdyby wszystko to odbywało się automatycznie, gdy obiekt nie jest już używany, za pomocą śmieciarza. W praktyce GC może brakować lub mieć braki, a rekompensuje to wyraźne uruchomienie programu finalizacji i odzyskiwania.
Wyraźne wyzwalanie przez program stanowi problem, ponieważ może utrudniać analizę błędów programowania, gdy obiekt będący nadal w użyciu jest jawnie kończony.
Dlatego o wiele lepiej polegać na automatycznym usuwaniu śmieci w celu odzyskania zasobów. Ale są dwa problemy:
niektóre techniki czyszczenia pamięci pozwalają na wycieki pamięci, które uniemożliwiają pełne odzyskanie zasobów. Jest to dobrze znane z GC do zliczania referencji, ale może pojawić się w przypadku innych technik GC przy korzystaniu z niektórych organizacji danych bez opieki (punkt nie omawiany tutaj).
podczas gdy technika GC może być dobra w identyfikowaniu nieużywanych zasobów pamięci, finalizacja zawartych w nich obiektów może nie być prosta, co komplikuje problem odzyskiwania innych zasobów używanych przez te obiekty, co często jest celem finalizacji.
Wreszcie ważną kwestią często zapominaną jest to, że cykle GC mogą być wyzwalane przez wszystko, nie tylko przez brak pamięci, jeśli zapewnione są odpowiednie haki i jeśli koszt cyklu GC jest uważany za warty zachodu. Dlatego inicjowanie GC jest całkowicie w porządku, gdy brakuje jakiegoś zasobu, w nadziei na jego uwolnienie.
Odliczanie liczników śmieci
Zliczanie referencji to słaba technika zbierania śmieci , która nie obsługuje poprawnie cykli. Byłoby rzeczywiście słabe w niszczeniu przestarzałych struktur i odzyskiwaniu innych zasobów tylko dlatego, że jest słabe w odzyskiwaniu pamięci. Jednak finalizatory mogą być najłatwiejsze w użyciu z urządzeniem do odmierzania śmieci (GC), ponieważ GC zlicza liczbę referencji odzyskuje strukturę, gdy jego liczba ref spada do 0, w którym to czasie jego adres jest znany wraz z jego typem, albo statycznie lub dynamicznie. Dlatego możliwe jest odzyskanie pamięci dokładnie po zastosowaniu odpowiedniego finalizatora i wywołaniu rekurencyjnie procesu na wszystkich wskazanych obiektach (być może poprzez procedurę finalizacji).
Mówiąc w skrócie, finalizacja jest łatwa do wdrożenia przy pomocy GC zliczającego Ref, ale cierpi z powodu „niekompletności” GC, w rzeczywistości z powodu okrągłych struktur, dokładnie w takim samym stopniu, w jakim cierpi odzyskanie pamięci. Innymi słowy, jeśli chodzi o liczbę referencji, pamięć jest dokładnie tak źle zarządzana, jak inne zasoby, takie jak gniazda, uchwyty plików itp.
Rzeczywiście, niemożność odzyskania struktur zapętlonych przez GC (ogólnie) może być postrzegana jako wyciek pamięci . Nie można oczekiwać, że wszystkie GC unikną wycieków pamięci. Zależy to od algorytmu GC i dynamicznie dostępnych informacji o strukturze typu (na przykład w konserwatywnym GC ).
Śledzenie śmieciarek
Potężniejsza rodzina GC, bez takich przecieków, to rodzina śledzenia, która bada żywe części pamięci, zaczynając od dobrze zidentyfikowanych wskaźników głównych. Wszystkie części pamięci, które nie są odwiedzane w tym procesie śledzenia (które można faktycznie rozłożyć na różne sposoby, ale muszę to uprościć) są nieużywanymi częściami pamięci, które można w ten sposób odzyskać 1 . Te kolektory odzyskają wszystkie części pamięci, do których program nie może uzyskać dostępu, bez względu na to, co robi. Odzyskuje struktury kołowe, a bardziej zaawansowane GC opierają się na pewnej odmianie tego paradygmatu, czasem bardzo wyrafinowanej. W niektórych przypadkach można go połączyć z liczeniem referencyjnym i zrekompensować jego słabości.
Problem polega na tym, że twoje stwierdzenie (na końcu pytania):
jest technicznie niepoprawny do śledzenia kolektorów.
Ze 100% pewnością wiadomo, które części pamięci nie są już używane . (Mówiąc dokładniej, należy powiedzieć, że nie są już dostępne , ponieważ niektóre części, których nie można już używać zgodnie z logiką programu, są nadal uważane za używane, jeśli w programie nadal znajduje się bezużyteczny wskaźnik dane.) Ale potrzebne są dalsze przetwarzanie i odpowiednie struktury, aby wiedzieć, jakie nieużywane obiekty mogły być przechowywane w tych obecnie nieużywanych częściach pamięci . Nie można tego ustalić na podstawie tego, co wiadomo o programie, ponieważ program nie jest już połączony z tymi częściami pamięci.
Tak więc po przejściu procesu wyrzucania elementów bezużytecznych pozostają fragmenty pamięci, które zawierają obiekty, które nie są już w użyciu, ale z góry nie ma sposobu, aby wiedzieć, jakie są te obiekty, aby zastosować poprawną finalizację. Ponadto, jeśli kolektor śledzący jest typu znacznika i przeciągnięcia, może być tak, że niektóre fragmenty mogą zawierać obiekty, które zostały już sfinalizowane w poprzednim przebiegu GC, ale nie były używane od tego czasu ze względu na fragmentację. Można to jednak rozwiązać za pomocą rozszerzonego jawnego pisania.
Podczas gdy prosty kolektor po prostu odzyskałby te fragmenty pamięci, bez zbędnych ceregieli, finalizacja wymaga specjalnego przejścia w celu zbadania tej nieużywanej pamięci, zidentyfikowania zawartych w niej obiektów i zastosowania procedur finalizacji. Ale takie badanie wymaga określenia rodzaju przechowywanych tam obiektów, a określenie typu jest również potrzebne do zastosowania właściwej finalizacji, jeśli taka istnieje.
Oznacza to dodatkowe koszty w czasie GC (dodatkowe przejście) i ewentualnie dodatkowe koszty pamięci w celu udostępnienia odpowiednich informacji o typie podczas tego przejścia za pomocą różnych technik. Koszty te mogą być znaczące, ponieważ często chce się sfinalizować tylko kilka obiektów, a czas i przestrzeń nad głową mogą dotyczyć wszystkich obiektów.
Inną kwestią jest to, że narzut czasu i przestrzeni może dotyczyć wykonania kodu programu, a nie tylko wykonania GC.
Nie mogę udzielić bardziej precyzyjnej odpowiedzi, wskazując na konkretne problemy, ponieważ nie znam specyfiki wielu wymienionych języków. W przypadku C pisanie jest bardzo trudnym zagadnieniem, które prowadzi do rozwoju konserwatywnych kolektorów. Domyślam się, że wpływa to również na C ++, ale nie jestem ekspertem w C ++. Potwierdza to Hans Boehm, który przeprowadził wiele badań dotyczących konserwatywnej GC. Konserwatywny GC nie może odzyskać systematycznie całej nieużywanej pamięci właśnie dlatego, że może brakować dokładnych informacji o typie danych. Z tego samego powodu nie byłby w stanie systematycznie stosować procedur finalizacyjnych.
Można więc robić to, o co prosisz, jak wiesz z niektórych języków. Ale nie przychodzi za darmo. W zależności od języka i jego implementacji może to wiązać się z kosztami, nawet jeśli nie korzystasz z tej funkcji. Aby rozwiązać te problemy, można rozważyć różne techniki i kompromisy, ale wykracza to poza zakres rozsądnej odpowiedzi.
1 - jest to abstrakcyjna prezentacja kolekcji śledzenia (obejmującej zarówno kopiowanie, jak i oznaczanie i przeglądanie GC), rzeczy różnią się w zależności od typu kolektora śledzenia, a eksploracja nieużywanej części pamięci jest różna, w zależności od tego, czy kopia, czy znak i zamiatanie jest używane.
źródło
getting memory recycled
, którą nazywamreclamation
, i przeprowadzając wcześniej pewne porządki, takie jak odzyskiwanie innych zasobów lub aktualizowanie niektórych tabel obiektów, które nazywamfinalization
. Wydawało mi się, że są to istotne kwestie, ale mogłem nie zauważyć punktu w waszej terminologii, który był dla mnie nowy.Wzorzec destruktora obiektów ma podstawowe znaczenie dla obsługi błędów w programowaniu systemów, ale nie ma nic wspólnego z odśmiecaniem. Ma raczej związek z dopasowaniem czasu życia obiektu do zakresu i może być implementowany / używany w dowolnym języku, który ma funkcje pierwszej klasy.
Przykład (pseudokod). Załóżmy, że masz typ „surowego pliku”, taki jak typ deskryptora pliku Posix. Istnieją cztery podstawowe operacje,
open()
,close()
,read()
,write()
. Chcesz zaimplementować „bezpieczny” typ pliku, który zawsze czyści po sobie. (Tj., Który ma automatyczny konstruktor i destruktor.)Będę zakładać nasz język posiada obsługę wyjątków z
throw
,try
ifinally
(w językach bez wyjątku obsługi można założyć dyscypliny gdzie użytkownik typu zwraca szczególną wartość, aby wskazać błąd.)Skonfigurujesz funkcję, która akceptuje funkcję wykonującą pracę. Funkcja procesu roboczego przyjmuje jeden argument (uchwyt pliku „bezpiecznego”).
Zapewniasz również implementacje
read()
iwrite()
dlasafe_file
(które po prostu wywołująraw_file
read()
iwrite()
). Teraz użytkownik używa takiegosafe_file
typu:Destruktor C ++ jest tak naprawdę tylko składniowym cukrem dla
try-finally
bloku. Prawie wszystko, co tutaj zrobiłem, to konwersja do tego, cosafe_file
skompilowaliby klasa C ++ z konstruktorem i destruktorem. Zauważ, że C ++ nie mafinally
wyjątków, szczególnie dlatego , że Stroustrup uważał, że użycie jawnego destruktora jest lepsze składniowo (i wprowadził go do języka, zanim język miał funkcje anonimowe).(Jest to uproszczenie jednego ze sposobów, w jaki ludzie od wielu lat zajmują się obsługą błędów w językach podobnych do Lisp. Myślę, że po raz pierwszy wpadłem na to pod koniec lat 80. lub na początku lat 90., ale nie pamiętam gdzie).
źródło
safe_file
iwith_file_opened_for_read
(obiekt, który zamyka się, gdy wychodzi poza zakres ). To ważne, że nie ma takiej samej składni, jak konstruktory, nie ma znaczenia. Lisp, Scheme, Java, Scala, Go, Haskell, Rust, JavaScript, Clojure wszystkie obsługują wystarczające funkcje pierwszej klasy, więc nie potrzebują destruktorów, aby zapewnić tę samą przydatną funkcję.To nie jest pełna odpowiedź na pytanie, ale chciałem dodać kilka uwag, które nie zostały uwzględnione w innych odpowiedziach lub komentarzach.
Pytanie domyślnie zakłada, że mówimy o języku obiektowym w stylu Simuli, który sam w sobie jest ograniczający. W większości języków, nawet tych z przedmiotami, nie wszystko jest przedmiotem. Maszyna do implementacji destruktorów wiązałaby się z kosztami, które nie każdy implementator języka jest skłonny zapłacić.
C ++ ma pewne dorozumiane gwarancje dotyczące kolejności zniszczenia. Jeśli masz na przykład drzewiastą strukturę danych, dzieci zostaną zniszczone przed rodzicem. Nie dzieje się tak w przypadku języków GC, więc zasoby hierarchiczne mogą zostać zwolnione w nieprzewidywalnej kolejności. W przypadku zasobów innych niż pamięć może to mieć znaczenie.
źródło
Kiedy projektowano dwie najpopularniejsze frameworki GC (Java i .NET), myślę, że autorzy spodziewali się, że finalizacja zadziała wystarczająco dobrze, aby uniknąć potrzeby innych form zarządzania zasobami. Wiele aspektów projektowania języka i frameworka można znacznie uprościć, jeśli nie są potrzebne wszystkie funkcje niezbędne do zapewnienia 100% niezawodnego i deterministycznego zarządzania zasobami. W C ++ konieczne jest rozróżnienie pojęć:
Wskaźnik / referencja, która identyfikuje obiekt, który jest wyłącznie własnością posiadacza referencji i który nie jest identyfikowany przez żadne wskazówki / referencje, o których właściciel nie wie.
Wskaźnik / odnośnik identyfikujący obiekt, który można udostępnić, który nie jest wyłączną własnością nikogo.
Wskaźnik / referencja, która identyfikuje obiekt, który jest wyłącznie własnością posiadacza referencji, ale do którego może być dostępny poprzez „widoki”, właściciel nie ma możliwości śledzenia.
Wskaźnik / referencja, która identyfikuje obiekt, który zapewnia widok obiektu będącego własnością innej osoby.
Jeśli język / środowisko GC nie musi martwić się o zarządzanie zasobami, wszystkie powyższe elementy można zastąpić jednym rodzajem odniesienia.
Uważam za naiwny pomysł, że finalizacja wyeliminuje potrzebę innych form zarządzania zasobami, ale bez względu na to, czy takie oczekiwanie było wówczas uzasadnione, historia pokazała, że istnieje wiele przypadków, które wymagają bardziej precyzyjnego zarządzania zasobami, niż przewiduje to finalizacja . Zdarza mi się myśleć, że korzyści z uznania własności na poziomie języka / struktury byłyby wystarczające, aby uzasadnić koszt (złożoność musi gdzieś istnieć, a przeniesienie go do języka / struktury uprościłoby kod użytkownika), ale zdaję sobie sprawę, że istnieją znaczące korzyści wynikające z zaprojektowania posiadania jednego „rodzaju” referencji - coś, co działa tylko wtedy, gdy język / framework są niezależne od problemów związanych z czyszczeniem zasobów.
źródło
Destruktor w C ++ faktycznie łączy dwie rzeczy . Zwalnia pamięć RAM i identyfikatory zasobów.
Inne języki oddzielają te obawy, ponieważ GC jest odpowiedzialna za zwalnianie pamięci RAM, podczas gdy inna funkcja językowa bierze pod uwagę zwalnianie identyfikatorów zasobów.
O to właśnie chodzi w GC. Nie donoszą tylko jednej rzeczy i ma to zapewnić, że nie zabraknie ci pamięci. Jeśli pamięć RAM jest nieskończona, wszystkie GC zostaną wycofane, ponieważ nie ma już żadnego rzeczywistego powodu, aby istniały.
Języki mogą zapewniać różne sposoby zwalniania identyfikatorów zasobów poprzez:
ręcznie
.CloseOrDispose()
rozproszone po kodzieręcznie
.CloseOrDispose()
rozproszone w ręcznym „finally
bloku”ręczne „resource bloki id” (czyli
using
,with
,try
-Z-resources , etc), które automatyzuje.CloseOrDispose()
po blok jest wykonywanegwarantowana „id” bloki zasobów, które automatyzuje
.CloseOrDispose()
po blok jest wykonywaneWiele języków korzysta z mechanizmów ręcznych (w przeciwieństwie do gwarantowanych), co stwarza okazję do niewłaściwego zarządzania zasobami. Weź ten prosty kod NodeJS:
... gdzie programista zapomniał zamknąć otwarty plik.
Tak długo, jak program będzie działał, otwarty plik utknie w zawieszeniu. Łatwo to zweryfikować, próbując otworzyć plik za pomocą HxD i sprawdzić, czy nie można tego zrobić:
Zwolnienie identyfikatorów zasobów w ramach niszczycieli C ++ również nie jest gwarantowane. Możesz myśleć, że RAII działa jak gwarantowane „bloki identyfikatora zasobów”, ale w przeciwieństwie do „bloków identyfikatora zasobów”, język C ++ nie powstrzymuje obiektu udostępniającego blok RAII przed wyciekiem , więc blok RAII może nigdy nie zostać wykonany .
Ponieważ zarządzają identyfikatorami zasobów w inny sposób, jak wspomniano powyżej.
Ponieważ zarządzają identyfikatorami zasobów w inny sposób, jak wspomniano powyżej.
Ponieważ zarządzają identyfikatorami zasobów w inny sposób, jak wspomniano powyżej.
Java nie ma destruktorów.
Dokumenty Java wspominają :
... ale umieszczenie kodu zarządzania identyfikatorem zasobu
Object.finalizer
jest w dużej mierze uważane za anty-wzorzec ( por .). Zamiast tego kod ten powinien zostać napisany na stronie połączenia.Dla osób, które używają anty-wzorca, ich uzasadnieniem jest to, że mogły zapomnieć o wydaniu identyfikatorów zasobów na stronie wywoływania. Dlatego robią to ponownie w finalizatorze, na wszelki wypadek.
Nie ma wielu przypadków użycia dla finalizatorów, ponieważ służą one do uruchomienia fragmentu kodu między momentem, gdy nie ma już żadnych silnych odniesień do obiektu, a czasem, kiedy GC odzyskuje jego pamięć.
Możliwym przypadkiem użycia jest sytuacja, gdy chcesz przechowywać dziennik czasu między obiektem gromadzonym przez GC a czasem, gdy nie ma już żadnych silnych odniesień do obiektu, takich jak:
źródło
znalazłem odnośnik na ten temat w Dr Dobbs wrt c ++, który ma bardziej ogólne idee, które dowodzą, że destruktory są problematyczne w języku, w którym są implementowane. z grubsza wydaje się, że głównym celem niszczycieli jest radzenie sobie z dezalokacją pamięci, co jest trudne do prawidłowego wykonania. pamięć jest przydzielana fragmentarycznie, ale różne obiekty łączą się, a następnie odpowiedzialność / granice delokalizacji nie są tak jasne.
więc rozwiązanie tego śmieciarza ewoluowało wiele lat temu, ale zbieranie śmieci nie opiera się na obiektach znikających z zasięgu przy wyjściu z metody (jest to koncepcja trudna do wdrożenia), ale na kolektorze działającym okresowo, nieco niedeterministycznie, gdy aplikacja odczuwa „presję pamięci” (tzn. kończy się pamięć).
innymi słowy, zwykła ludzka koncepcja „nowo nieużywanego obiektu” jest w pewnym sensie mylącą abstrakcją w tym sensie, że żaden przedmiot nie może „natychmiast” zostać nieużywany. nieużywane obiekty można „wykryć” tylko poprzez uruchomienie algorytmu wyrzucania elementów bezużytecznych, który przechodzi przez wykres odniesienia obiektu, a algorytmy o najlepszych parametrach działają z przerwami.
możliwe jest, że na algorytm czeka lepszy algorytm wyrzucania elementów bezużytecznych, który może niemal natychmiast zidentyfikować nieużywane obiekty, co może następnie prowadzić do spójnego kodu wywołującego destruktor, ale nie znaleziono go po wielu latach badań w tej dziedzinie.
rozwiązaniem obszarów zarządzania zasobami, takich jak pliki lub połączenia, wydaje się być posiadanie „menedżerów” obiektów, którzy próbują obsłużyć ich użycie.
źródło