Czy musisz pozbyć się obiektów i ustawić je na wartość zerową, czy też śmieciarz je wyczyści, gdy wyjdą poza zakres?
310
Czy musisz pozbyć się obiektów i ustawić je na wartość zerową, czy też śmieciarz je wyczyści, gdy wyjdą poza zakres?
Dispose()
metodzie! Jest to subtelna odmiana tego pytania, ale ważna, ponieważ obiekt usuwany nie może wiedzieć, czy „wychodzi poza zakres” (wywołanieDispose()
nie jest gwarancją). Więcej tutaj: stackoverflow.com/questions/6757048/…Odpowiedzi:
Obiekty zostaną wyczyszczone, gdy nie będą już używane i gdy śmieciarz uzna to za odpowiednie. Czasami może być konieczne ustawienie obiektu
null
, aby wykroczył poza zakres (na przykład pole statyczne, którego wartości już nie potrzebujesz), ale ogólnie nie ma potrzeby ustawiania gonull
.Jeśli chodzi o usuwanie przedmiotów, zgadzam się z @Andre. Jeśli obiekt jest
IDisposable
, dobrym pomysłem jest pozbycie się go, gdy nie jest już potrzebny, szczególnie jeśli obiekt używa niezarządzanych zasobów. Nie pozbywanie się niezarządzanych zasobów doprowadzi do wycieków pamięci .Możesz użyć
using
instrukcji, aby automatycznie pozbyć się obiektu, gdy Twój program opuści zakresusing
instrukcji.Co jest funkcjonalnie równoważne z:
źródło
if (obj != null) ((IDisposable)obj).Dispose();
IDisposable
. Nieusunięcie obiektu zazwyczaj nie spowoduje wycieku pamięci w żadnej dobrze zaprojektowanej klasie. Podczas pracy z niezarządzanymi zasobami w języku C # powinieneś mieć finalizator, który nadal zwalnia niezarządzane zasoby. Oznacza to, że zamiast zwalniać zasoby, kiedy należy to zrobić, zostanie on odroczony do momentu, gdy moduł odśmiecający sfinalizuje zarządzany obiekt. Nadal może jednak powodować wiele innych problemów (takich jak niepublikowane blokady). Powinieneś sięIDisposable
jednak pozbyć !Obiekty nigdy nie wychodzą poza zakres w języku C #, podobnie jak w języku C ++. Zajmuje się nimi Garbage Collector automatycznie, gdy nie są już używane. Jest to bardziej skomplikowane podejście niż C ++, w którym zakres zmiennej jest całkowicie deterministyczny. Moduł śmieciowy CLR aktywnie przechodzi przez wszystkie utworzone obiekty i sprawdza, czy są one używane.
Obiekt może wyjść „poza zakres” w jednej funkcji, ale jeśli jego wartość zostanie zwrócona, GC sprawdzi, czy funkcja wywołująca zachowuje wartość zwracaną.
Ustawienie odwołań do obiektu na
null
jest konieczne, ponieważ czyszczenie pamięci działa, sprawdzając, do których obiektów odwołują się inne obiekty.W praktyce nie musisz się martwić zniszczeniem, po prostu działa i jest świetne :)
Dispose
należy wywoływać we wszystkich obiektach, które implementująIDisposable
po zakończeniu pracy z nimi. Zwykle używałbyśusing
bloku z takimi obiektami:EDYTOWAĆ W zakresie zmiennym. Craig zapytał, czy zakres zmiennej ma jakikolwiek wpływ na czas życia obiektu. Aby poprawnie wyjaśnić ten aspekt CLR, muszę wyjaśnić kilka pojęć z C ++ i C #.
Rzeczywisty zakres zmiennej
W obu językach zmienna może być używana tylko w tym samym zakresie, w jakim została zdefiniowana - klasa, funkcja lub blok instrukcji ujęty w nawiasy klamrowe. Subtelna różnica polega jednak na tym, że w języku C # zmienne nie mogą być ponownie zdefiniowane w zagnieżdżonym bloku.
W C ++ jest to całkowicie legalne:
W języku C # pojawia się błąd kompilatora:
Ma to sens, jeśli spojrzysz na wygenerowany MSIL - wszystkie zmienne używane przez funkcję są zdefiniowane na początku funkcji. Spójrz na tę funkcję:
Poniżej znajduje się wygenerowana IL. Zauważ, że iVal2, który jest zdefiniowany wewnątrz bloku if, jest faktycznie zdefiniowany na poziomie funkcji. W efekcie oznacza to, że C # ma zasięg klasy i funkcji tylko w zakresie zmiennego czasu życia.
Zakres C ++ i czas życia obiektu
Ilekroć zmienna C ++ przydzielona na stosie wykracza poza zakres, zostaje zniszczona. Pamiętaj, że w C ++ możesz tworzyć obiekty na stosie lub na stercie. Kiedy tworzysz je na stosie, gdy wykonanie opuści zakres, są one wyskakiwane ze stosu i niszczone.
Kiedy obiekty C ++ są tworzone na stercie, muszą zostać jawnie zniszczone, w przeciwnym razie jest to wyciek pamięci. Jednak nie ma takiego problemu ze zmiennymi stosu.
C # Żywotność obiektu
W CLR obiekty (tj. Typy referencyjne) są zawsze tworzone na zarządzanej stercie. Jest to dodatkowo wzmocnione przez składnię tworzenia obiektów. Rozważ ten fragment kodu.
W C ++ spowoduje to utworzenie instancji na
MyClass
stosie i wywołanie jej domyślnego konstruktora. W języku C # tworzyłoby odwołanie do klasy,MyClass
które niczego nie wskazuje. Jedynym sposobem utworzenia instancji klasy jest użycienew
operatora:W pewien sposób obiekty C # są bardzo podobne do obiektów tworzonych przy użyciu
new
składni w C ++ - są tworzone na stercie, ale w przeciwieństwie do obiektów C ++, są zarządzane przez środowisko wykonawcze, więc nie musisz się martwić o ich zniszczenie.Ponieważ obiekty są zawsze na stercie, fakt, że odwołania do obiektów (tj. Wskaźniki) wykraczają poza zakres, staje się dyskusyjny. Przy ustalaniu, czy obiekt ma zostać zebrany, bierze się więcej czynników niż po prostu obecność odniesień do obiektu.
C # Odwołania do obiektów
Jon Skeet porównał odwołania do obiektów w Javie do kawałków łańcucha przymocowanych do balonu, którym jest obiekt. Ta sama analogia dotyczy odwołań do obiektów C #. Wskazują po prostu lokalizację stosu zawierającego obiekt. Zatem ustawienie wartości null nie ma bezpośredniego wpływu na czas życia obiektu, balon nadal istnieje, dopóki GC go nie „wyskoczy”.
Kontynuując analogię balonu, wydaje się logiczne, że gdy balon nie będzie do niego przymocowany, może zostać zniszczony. W rzeczywistości dokładnie tak działają obiekty zliczane referencjami w językach niezarządzanych. Tyle że to podejście nie działa zbyt dobrze w przypadku odwołań cyklicznych. Wyobraź sobie dwa balony, które są połączone sznurkiem, ale żaden balon nie ma sznurka do niczego innego. Zgodnie z prostymi regułami liczenia referencji oba nadal istnieją, mimo że cała grupa balonów jest „osierocona”.
Obiekty .NET przypominają balony helowe pod dachem. Gdy dach się otworzy (biegnie GC) - nieużywane balony odpływają, nawet jeśli grupy balonów są ze sobą powiązane.
.NET GC wykorzystuje kombinację generacyjnego GC oraz mark i sweep. Podejście generacyjne obejmuje środowisko wykonawcze faworyzujące obiekty, które zostały ostatnio przydzielone, ponieważ są one bardziej nieużywane, a oznaczanie i przeglądanie obejmuje środowisko wykonawcze przechodzące przez cały wykres obiektów i sprawdzanie, czy istnieją grupy obiektów, które nie są używane. To odpowiednio rozwiązuje problem zależności cyklicznej.
Ponadto .NET GC działa na innym wątku (tak zwanym wątku finalizatora), ponieważ ma sporo do zrobienia, a wykonanie tego w głównym wątku zakłóciłoby działanie programu.
źródło
Jak powiedzieli inni, zdecydowanie chcesz zadzwonić,
Dispose
jeśli klasa implementujeIDisposable
. Zajmuję w tej sprawie dość sztywne stanowisko. Niektórzy mogą twierdzić, żeDispose
naDataSet
przykład wezwanie jest bezcelowe, ponieważ rozmontowali go i zobaczyli, że nie zrobił nic sensownego. Ale myślę, że w tym argumencie jest wiele błędów.Przeczytaj to, aby poznać interesującą debatę szanowanych osób na ten temat. Następnie przeczytaj moje rozumowanie , dlaczego uważam, że Jeffery Richter jest w niewłaściwym obozie.
Teraz, czy powinieneś ustawić odniesienie do
null
. Odpowiedź brzmi nie. Pozwól mi zilustrować mój punkt za pomocą następującego kodu.Kiedy zatem uważasz, że obiekt, do którego się odwołuje,
a
kwalifikuje się do kolekcji? Jeśli powiedziałeś po wezwaniua = null
, to się mylisz. Jeśli powiedziałeś po zakończeniuMain
metody, to również się mylisz. Prawidłowa odpowiedź jest taka, że można ją odebrać w trakcie połączenia zDoSomething
. Zgadza się. Kwalifikuje się, zanim odniesienie zostanie ustawione na,null
a może nawet przed zakończeniem wezwania doDoSomething
zakończenia. Wynika to z faktu, że kompilator JIT może rozpoznać, że odwołania do obiektów nie są już usuwane z dereferencji, nawet jeśli nadal są zrootowane.źródło
a
pole prywatne jest członkiem klasy? Jeślia
nie jest ustawiony na zero, GC nie ma możliwości dowiedzenia się, czya
zostanie użyty ponownie w jakiejś metodzie, prawda? W ten sposóba
nie zostaną zebrane, dopóki nie zostanie zebrana cała klasa zawierająca. Nie?a
był członkiem klasy, a klasa zawierającaa
byłaby nadal zrootowana i używana, to i tak by się trzymała. To jest jeden scenariusz, w którym ustawienie gonull
może być korzystne.Dispose
jest ważny - nie można wywoływaćDispose
(ani żadnej innej metody niewłączalnej) obiektu bez odniesienia do niego; wywołanieDispose
po zakończeniu pracy przy użyciu obiektu zagwarantuje, że referencja zrootowana będzie istnieć przez czas trwania ostatniej wykonanej na niej akcji. Porzucenie wszystkich odwołań do obiektu bez wywoływaniaDispose
może ironicznie spowodować, że zasoby obiektu zostaną czasem zwolnione zbyt wcześnie .Nigdy nie musisz ustawiać obiektów na zero w języku C #. Kompilator i środowisko wykonawcze zajmą się ustaleniem, kiedy nie są już w zasięgu.
Tak, powinieneś pozbyć się obiektów, które implementują IDisposable.
źródło
want
go unieważnić, jak tylko skończysz, aby można go było odzyskać.Zgadzam się ze wspólną odpowiedzią tutaj, że tak, powinieneś się pozbyć i nie, ogólnie nie powinieneś ustawiać zmiennej na zero ... ale chciałem zauważyć, że utylizacja NIE dotyczy przede wszystkim zarządzania pamięcią. Tak, może pomóc (a czasem pomaga) w zarządzaniu pamięcią, ale jego głównym celem jest zapewnienie deterministycznego uwolnienia ograniczonych zasobów.
Na przykład, jeśli otworzysz port sprzętowy (na przykład szeregowy), gniazdo TCP / IP, plik (w trybie wyłącznego dostępu) lub nawet połączenie z bazą danych, uniemożliwisz innym kodom korzystanie z tych elementów do momentu ich zwolnienia. Dispose zazwyczaj wypuszcza te elementy (wraz z GDI i innymi uchwytami „os” itp., Których jest 1000 dostępnych, ale ogólnie są ograniczone). Jeśli nie wywołasz polecenia dipose na obiekcie właściciela i jawnie zwolnisz te zasoby, spróbuj ponownie otworzyć ten sam zasób w przyszłości (lub inny program to zrobi), ta próba otwarcia zakończy się niepowodzeniem, ponieważ Twój nienarzucony, niepobrany obiekt nadal ma otwarty element . Oczywiście, gdy GC zbierze przedmiot (jeśli wzorzec Dispose został zaimplementowany poprawnie) zasób zostanie zwolniony ... ale nie wiesz, kiedy to będzie, więc nie t wiedzieć, kiedy można bezpiecznie ponownie otworzyć ten zasób. Jest to podstawowy problem, z którego działa Dispose. Oczywiście, zwolnienie tych uchwytów często zwalnia pamięć i nigdy ich nie zwolnienie może nigdy nie zwolnić tej pamięci ... stąd cała mowa o wyciekach pamięci lub opóźnieniach w czyszczeniu pamięci.
Widziałem przykłady tego powodujące problemy w świecie rzeczywistym. Na przykład widziałem aplikacje sieci Web ASP.Net, które ostatecznie nie mogą połączyć się z bazą danych (choć przez krótki czas lub do momentu ponownego uruchomienia procesu serwera WWW), ponieważ pula połączeń serwera SQL jest pełna ... , tak wiele połączeń zostało utworzonych i nie zostało jawnie zwolnionych w tak krótkim czasie, że nie można utworzyć nowych połączeń, a wiele połączeń w puli, chociaż nie jest aktywnych, nadal jest przywoływanych przez niepozbyte i niepobrane obiekty, dlatego też „ zostać ponownie wykorzystane. Prawidłowe usunięcie połączeń z bazą danych tam, gdzie to konieczne, zapewnia, że ten problem nie wystąpi (przynajmniej nie, jeśli masz bardzo wysoki równoczesny dostęp).
źródło
Jeśli obiekt implementuje
IDisposable
, to tak, należy go usunąć. Obiekt może zawieszać się na zasobach macierzystych (uchwyty plików, obiekty systemu operacyjnego), które w innym przypadku mogą nie zostać natychmiast zwolnione. Może to prowadzić do głodu zasobów, problemów z blokowaniem plików i innych subtelnych błędów, których można by uniknąć.Zobacz także Implementowanie metody usuwania w MSDN.
źródło
Dispose
zostanie wywołany. Ponadto, jeśli twój obiekt trzyma ograniczony zasób lub blokuje jakiś zasób (np. Plik), to chcesz go zwolnić jak najszybciej. Oczekiwanie, aż GC to zrobi, jest nieoptymalne.might
dzwoń, alewill
dzwoń.Jeśli implementują interfejs IDisposable, należy je zutylizować. Śmieciarka zajmie się resztą.
EDYCJA: najlepiej jest użyć
using
polecenia podczas pracy z przedmiotami jednorazowymi:źródło
Kiedy obiekt się implementuje
IDisposable
, powinieneś wywołaćDispose
(lubClose
, w niektórych przypadkach, wywoła to dla ciebie Dispose).Zwykle nie musisz ustawiać obiektów
null
, ponieważ GC będzie wiedział, że obiekt nie będzie już używany.Jest jeden wyjątek, kiedy ustawiam obiekty na
null
. Kiedy pobieram wiele obiektów (z bazy danych), nad którymi muszę pracować, i przechowuję je w kolekcji (lub tablicy). Kiedy „praca” jest wykonywana, ustawiam obiekt nanull
, ponieważ GC nie wie, że skończyłem z nim pracować.Przykład:
źródło
Zwykle nie trzeba ustawiać pól na zero. Zawsze jednak zalecałbym pozbywanie się niezarządzanych zasobów.
Z doświadczenia radzę również wykonać następujące czynności:
Natknąłem się na bardzo trudne do znalezienia problemy, które były bezpośrednim wynikiem nieprzestrzegania powyższych rad.
Dobrym miejscem do tego jest Dispose (), ale wcześniej jest zwykle lepiej.
Ogólnie rzecz biorąc, jeśli istnieje odwołanie do obiektu, moduł czyszczenia pamięci (GC) może potrwać kilka pokoleń dłużej, aby stwierdzić, że obiekt nie jest już używany. Cały czas obiekt pozostaje w pamięci.
To może nie stanowić problemu, dopóki nie zauważysz, że Twoja aplikacja używa dużo więcej pamięci, niż można się spodziewać. Gdy tak się stanie, podłącz profiler pamięci, aby zobaczyć, które obiekty nie są czyszczone. Ustawienie pól odwołujących się do innych obiektów do wartości zerowej i usuwanie zbędnych zbiorów może naprawdę pomóc GC dowiedzieć się, jakie obiekty można usunąć z pamięci. GC szybciej odzyska zużytą pamięć, dzięki czemu Twoja aplikacja będzie mniej głodna i szybsza.
źródło
Zawsze nazywaj utylizacją. To nie jest warte ryzyka. Duże zarządzane aplikacje korporacyjne powinny być traktowane z szacunkiem. Nie można poczynić żadnych założeń, bo wróci cię ugryźć.
Nie słuchaj leppie.
Wiele obiektów tak naprawdę nie implementuje IDisposable, więc nie musisz się o nie martwić. Jeśli rzeczywiście wykroczą poza zakres, zostaną automatycznie uwolnieni. Nigdy też nie spotkałem się z sytuacją, w której musiałem ustawić coś na zero.
Jedną rzeczą, która może się zdarzyć, jest to, że wiele przedmiotów może być otwartych. Może to znacznie zwiększyć wykorzystanie pamięci przez aplikację. Czasami trudno jest ustalić, czy to w rzeczywistości wyciek pamięci, czy też aplikacja robi po prostu wiele rzeczy.
Narzędzia profilu pamięci mogą pomóc w takich sprawach, ale może być trudne.
Ponadto zawsze anuluj subskrypcję niepotrzebnych wydarzeń. Uważaj również na wiązanie i kontrolki WPF. Nie jest to zwykła sytuacja, ale natknąłem się na sytuację, w której miałem kontrolę WPF, która była związana z obiektem leżącym u podstaw. Podstawowy obiekt był duży i zajmował dużą ilość pamięci. Kontrolkę WPF zastępowano nową instancją, a stara z jakiegoś powodu nadal się kręciła. Spowodowało to duży wyciek pamięci.
Na tylnej stronie kod był źle napisany, ale chodzi o to, że chcesz się upewnić, że rzeczy, które nie są używane, wykraczają poza zakres. Znalezienie tego za pomocą narzędzia do profilowania pamięci zajęło dużo czasu, ponieważ trudno jest wiedzieć, jakie rzeczy w pamięci są ważne i czego nie powinno tam być.
źródło
Ja też muszę odpowiedzieć. JIT generuje tabele wraz z kodem na podstawie statycznej analizy wykorzystania zmiennych. Te wpisy tabeli są „GC-Roots” w bieżącej ramce stosu. W miarę przesuwania się wskaźnika instrukcji wpisy w tabeli stają się niepoprawne i gotowe do odśmiecania. Dlatego: Jeśli jest to zmienna o zasięgu, nie musisz ustawiać jej na null - GC zbierze obiekt. Jeśli jest to element członkowski lub zmienna statyczna, musisz ustawić wartość null
źródło