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:
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?
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):
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.
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.
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.