Zrozumienie zbierania śmieci in.NET

Rozważ poniższy kod:

public class Class1
{
    public static int c;
    ~Class1()
    {
        c++;
    }
}

public class Class2
{
    public static void Main()
    {
        {
            var c1=new Class1();
            //c1=null; // If this line is not commented out, at the Console.WriteLine call, it prints 1.
        }
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine(Class1.c); // prints 0
        Console.Read();
    }
}

Teraz, nawet jeśli zmienna c1 w głównej metodzie jest poza zakresem i nie odwołuje się dalej przez żaden inny obiekt po wywołaniu GC.Collect(), Dlaczego nie jest tam finalizowana?

Author: Peter Mortensen, 2013-06-16

3 answers

Jesteś potknięty i wyciągasz bardzo błędne wnioski, ponieważ używasz debuggera. Musisz uruchomić kod tak, jak działa on na komputerze użytkownika. Przełącz się najpierw na Release build za pomocą Build + Configuration manager, Zmień combo" Active solution configuration "w lewym górnym rogu Na"Release". Następnie przejdź do Narzędzia + Opcje, debugowanie, ogólne i odznacz opcję "eliminuj optymalizację JIT".

Teraz uruchom ponownie program i majstruj z kod źródłowy. Zauważ, że dodatkowe szelki nie mają żadnego efektu. I zauważ, że ustawienie zmiennej na null nie robi żadnej różnicy. Zawsze będzie drukować "1". Teraz działa tak, jak masz nadzieję i oczekiwałeś, że zadziała.

Co pozostawia zadanie wyjaśnienia, dlaczego działa tak inaczej, gdy uruchamiasz kompilację debugowania. Wymaga to wyjaśnienia, w jaki sposób garbage collector odkrywa zmienne lokalne i jaki wpływ na to ma obecność debuggera.

Po pierwsze, jitter wykonuje dwa ważne zadania, gdy kompiluje IL dla metody do kodu maszynowego. Pierwszy z nich jest bardzo widoczny w debugerze, możesz zobaczyć kod maszynowy w oknie Debug + Windows + Disassembly. Drugi obowiązek jest jednak całkowicie niewidoczny. Generuje również tabelę, która opisuje, w jaki sposób są używane zmienne lokalne wewnątrz ciała metody. Tabela ta zawiera wpis dla każdego argumentu metody oraz zmienną lokalną z dwoma adresami. Adres, pod którym zmienna będzie najpierw zapisz odniesienie do obiektu. Oraz adres instrukcji kodu maszynowego, gdzie ta zmienna nie jest już używana. Również czy ta zmienna jest przechowywana na ramce stosu czy w rejestrze procesora.

Ta tabela jest niezbędna dla garbage collector, musi wiedzieć, gdzie szukać odniesień do obiektów podczas wykonywania kolekcji. Dość łatwe do zrobienia, gdy Referencja jest częścią obiektu na stercie GC. Zdecydowanie nie jest to łatwe, gdy odniesienie do obiektu jest przechowywane w rejestrze procesora. Stół mówi, gdzie szukać.

Adres "nieużywany" w tabeli jest bardzo ważny. To sprawia, że garbage collector jest bardzo wydajny . Może zbierać odniesienia do obiektu, nawet jeśli jest ono używane wewnątrz metody i ta metoda nie zakończyła jeszcze wykonywania. Co jest bardzo powszechne, na przykład Twoja metoda Main() zatrzyma wykonywanie tylko tuż przed zakończeniem programu. Oczywiście nie chcesz, aby jakiekolwiek odniesienia do obiektów użyte w tej metodzie Main() żyły dla czas trwania programu, co oznaczałoby wyciek. Jitter może użyć tabeli, aby odkryć, że taka zmienna lokalna nie jest już użyteczna, w zależności od tego, jak daleko program postępował wewnątrz tej metody Main () przed wykonaniem wywołania.

Niemal magiczną metodą związaną z tą tabelą jest GC.KeepAlive (). Jest to specjalna metoda bardzo , w ogóle nie generuje żadnego kodu. Jego jedynym obowiązkiem jest modyfikacja tej tabeli. przedłuża okres życia zmiennej lokalnej, zapobieganie zbieraniu śmieci przez przechowywany w nim odnośnik. Jedyny czas potrzebny na jego użycie to powstrzymanie GC przed zbytnim pobieraniem referencji, co może się zdarzyć w scenariuszach interop, w których Referencja jest przekazywana do kodu niezarządzanego. Garbage collector nie widzi takich odniesień używanych przez taki kod, ponieważ nie został skompilowany przez jitter, więc nie ma tabeli, która mówi, gdzie szukać odniesienia. Przekazywanie obiektu delegata do niezarządzanej funkcji, takiej jak EnumWindows () to przykład, kiedy musisz użyć GC.KeepAlive ().

Tak więc, jak można stwierdzić z przykładowego fragmentu po uruchomieniu go w Release build, zmienne lokalne mogą zostać zebrane wcześnie, zanim metoda zakończy wykonywanie. Co więcej, obiekt może zostać zebrany, gdy jedna z jego metod działa, jeśli ta metoda nie odnosi się już do to . Jest z tym problem, bardzo niewygodne jest debugowanie takiej metody. Ponieważ można dobrze umieścić zmienna w oknie obserwacyjnym lub sprawdź ją. I zniknie podczas debugowania, jeśli wystąpi GC. To byłoby bardzo nieprzyjemne, więc jitter jest świadomy istnienia dołączonego debuggera. Następnie modyfikuje tabelę i zmienia adres "ostatnio użyty". I zmienia ją z wartości normalnej na adres ostatniej instrukcji w metodzie. Co utrzymuje zmienną przy życiu tak długo, jak metoda nie wróciła. Co pozwala na ciągłe oglądanie dopóki metoda nie powróci.

To teraz wyjaśnia również to, co widziałeś wcześniej i dlaczego zadałeś pytanie. Wypisuje "0", ponieważ GC.Połączenie zbiorcze nie może zebrać referencji. Tabela mówi, że zmienna jest w użyciu past GC.Wywołanie Collect (), aż do końca metody. Zmuszony do tego przez dołączenie debuggera i poprzez uruchomienie kompilacji debugera.

Ustawienie zmiennej NA null ma teraz wpływ, ponieważ GC sprawdzi zmienna i nie będzie już widzieć referencji. Ale upewnij się, że nie wpadniesz w pułapkę, w którą wpadło wielu programistów C#, w rzeczywistości pisanie tego kodu było bezcelowe. Nie ma znaczenia, czy ta instrukcja jest obecna podczas uruchamiania kodu w Release build. W rzeczywistości optymalizator jitter usunie to oświadczenie, ponieważ nie ma żadnego efektu. Więc pamiętaj, aby nie pisać kodu w ten sposób, nawet jeśli wydawało się mieć efekt.


Ostatnia uwaga na ten temat, to sprawia, że programiści mają kłopoty, którzy piszą małe programy, aby zrobić coś z aplikacją biurową. Debugger zazwyczaj wprowadza ich na złą ścieżkę, chcą, aby program Office wyszedł na żądanie. Odpowiednim sposobem jest wywołanie GC.Collect (). Ale odkryją, że to nie działa, gdy debugują swoją aplikację, prowadząc ich do never-never land, dzwoniąc do Marshala.ReleaseComObject (). Ręczne zarządzanie pamięcią, it rzadko działa poprawnie, ponieważ łatwo przeoczy niewidoczny interfejs odniesienia. GC.Funkcja Collect() faktycznie działa, ale nie podczas debugowania aplikacji.

 314
Author: Hans Passant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-16 16:11:55

[ chciałem tylko dodać więcej na temat wewnętrznych procesów finalizacji]

Tak więc, tworzysz obiekt i kiedy obiekt zostanie zebrany, powinna zostać wywołana metoda Finalize obiektu. Ale jest coś więcej do finalizacji niż to bardzo proste założenie.

KRÓTKIE KONCEPCJE::

  1. Obiektów nie implementujących metod Finalize, tam pamięć jest w przeciwieństwie do poprzednich części,nie można ich odzyskać.]} kod aplikacji anymore

  2. Objects implementing Finalize Method, the Concept/Implementation z Application Roots, Finalization Queue, Freacheable Queue comes zanim zostaną odzyskane.

  3. Każdy obiekt jest uważany za śmieci, jeśli nie jest możliwy do osiągnięcia przez aplikację Kod

Assume:: Classes / Objects A, B, D, G, H do NOT implementation Finalize Method and C, E, F, I, J implementation Finalize Method.

Gdy aplikacja tworzy nowy obiekt, nowy operator przydziela pamięć z / align = "left" / jeżeli typ obiektu zawiera metodę Finalize, wtedy w kolejce finalizacji znajduje się wskaźnik do obiektu.

dlatego wskaźniki do obiektów C, E, F, I, J są dodawane do kolejki finalizacji.

Na Kolejka Finałowa jest wewnętrzną strukturą danych kontrolowaną przez garbage collector. Każdy wpis w kolejce wskazuje na obiekt, który powinien mieć wywołaną metodę Finalize zanim pamięć obiektu może być odzyskane. Rysunek poniżej przedstawia stertę zawierającą kilka obiektów. Niektóre z tych obiektów są dostępne z korzenie aplikacji, a niektórzy nie. Gdy obiekty C, E, F, I I J zostały utworzone,. Net Framework wykrywa, że te obiekty mają metody Finalize i wskaźniki do tych obiektów są dodawane do Kolejka Finałowa.

Tutaj wpisz opis obrazka

Gdy występuje GC (1st Collection), obiekty B, E, G, H, I I J są określane jako śmieci. ponieważ A, C, D, F są nadal dostępne za pomocą kodu aplikacji przedstawionego strzałkami z żółtego pola powyżej.

Garbage collector skanuje Kolejka Finałowa Szukam wskaźników do tych obiektów. gdy wskaźnik zostanie znaleziony, wskaźnik jest usuwany z kolejki finalizacji i dołączany do kolejki freachable ("F-reachable").

na Kolejka Darmowa jest inną wewnętrzną strukturą danych kontrolowaną przez śmieciarza. Każdy wskaźnik w Kolejka Darmowa identyfikuje obiekt, który jest gotowy do wywołania swojej metody Finalize.

Po zbiorze (1st Collection) sterta zarządzana wygląda podobnie jak na rysunku poniżej. Wyjaśnienie podane poniżej:
1.) pamięć zajmowana przez obiekty B, G I H została odzyskana natychmiast, ponieważ obiekty te nie posiadały metody finalize, która potrzebne do wywołania .

2.) jednak pamięć zajmowana przez obiekty E, I I J nie mogła być odzyskane, ponieważ ich metoda Finalize nie została jeszcze wywołana. Wywołanie metody Finalize odbywa się poprzez kolejki.

3.) A, C, D, F są nadal dostępne za pomocą kodu aplikacji przedstawionego przez strzałki z żółtego pola powyżej, więc nie będą zbierane w żadnym case

Tutaj wpisz opis obrazka

istnieje specjalny wątek runtime poświęcony wywołanie metody Finalize. Gdy kolejka freachable jest pusta (co zwykle ma miejsce), wątek usypia. Ale gdy pojawią się wpisy, wątek ten budzi się, usuwa każdy wpis z kolejki i wywołuje metodę Finalize każdego obiektu. Moduł garbage collector kompresuje pamięć odzyskiwalną, a specjalny wątek runtime opróżnia kolejkę freachable , wykonując metodę Finalize każdego obiektu. więc oto, kiedy zostanie wykonana twoja metoda Finalize

Następnym razem garbage collector jest wywoływany (2nd Collection), widzi, że ukończone obiekty są naprawdę śmieciami, ponieważ korzenie aplikacji nie wskazują na nie, a freachable queue nie wskazuje na nią(jest też pusta), dlatego pamięć dla obiektów (E, I, J) jest po prostu odzyskiwana ze sterty.Zobacz rysunek poniżej i porównaj go z rysunkiem tuż powyżej

Tutaj wpisz opis obrazka

Ważne jest, aby zrozumieć tutaj, że dwa GCs są wymagane, aby odzyskać pamięć używaną przez obiekty wymagające finalizacji . W rzeczywistości wymagane są nawet więcej niż dwa zbiory cab, ponieważ obiekty te mogą być promowane do starszej generacji

Uwaga:: Kolejka freachable queue {[20] } jest uważana za root, podobnie jak zmienne globalne i statyczne są roots. Dlatego jeśli obiekt znajduje się w kolejce freachable, to obiekt jest osiągalny i nie jest śmieciem.

Jako ostatnia uwaga, pamiętaj, że debugowanie aplikacji jest jedno, wywóz śmieci to co innego i działa inaczej. Do tej pory nie możesz czuć się śmieciarzem tylko przez debugowanie aplikacji, Ponadto jeśli chcesz zbadać pamięć, rozpocznij tutaj.

 29
Author: R.C,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-10 10:44:39

Istnieją 3 sposoby implementacji zarządzania pamięcią:-

GC działa tylko dla zarządzanych zasobów, dlatego. Net zapewnia Dispose i Finalize, aby zwolnić niezarządzane zasoby, takie jak strumień, połączenie z bazą danych, obiekty COM itp..

1) Usunąć

Dispose musi być wywołane jawnie dla typów, które implementują IDisposable.

Programista musi wywołać to za pomocą Dispose () lub za pomocą construct

Użyj GC.SuppressFinalize (this), aby zapobiec wywołaniu do Finalizer jeśli używałeś już dispose ()

2) Finalize or Distructor

Jest wywoływany niejawnie po tym, jak obiekt kwalifikuje się do oczyszczenia, finalizer dla obiektów jest wywoływany sekwencyjnie przez wątek finalizera.

Wadą implementacji finalizera jest to, że odzyskiwanie pamięci jest opóźnione, ponieważ finalizer dla takich klas/typów musi być wywoływany przed czyszczeniem, więc dodatkowy colect do odzyskiwania pamięci.

3) GC.Collect ()

Korzystanie z GC.Collect() niekoniecznie stawia GC w przypadku kolekcji, GC może nadal nadpisywać i uruchamiać, kiedy tylko chce.

Również GC.Collect() uruchomi tylko część śledzenia garbage collection i doda elementy do kolejki finalizera, ale nie wywoła finalizatorów dla typów, które są obsługiwane przez inny wątek.

Użyj WaitForPendingFinalizers, jeśli chcesz się upewnić, że wszystkie finalizery zostały wywołane po wywołaniu GC.Collect ()

 1
Author: Pankaj Singh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-13 13:35:16