2013-02-06 13 views
9

Rozważmy poniższy kod:Dlaczego moduł czyszczący C# nie próbuje dalej zwolnić pamięci, dopóki żądanie nie zostanie spełnione?

using System; 

namespace memoryEater 
{ 
    internal class Program 
    { 
     private static void Main(string[] args) 
     { 
      Console.WriteLine("alloc 1"); 
      var big1 = new BigObject(); 

      Console.WriteLine("alloc 2"); 
      var big2 = new BigObject(); 

      Console.WriteLine("null 1"); 
      big1 = null; 

      //GC.Collect(); 

      Console.WriteLine("alloc3"); 
      big1 = new BigObject(); 

      Console.WriteLine("done"); 
      Console.Read(); 
     } 
    } 

    public class BigObject 
    { 
     private const uint OneMeg = 1024 * 1024; 
     private static int _idCnt; 
     private readonly int _myId; 
     private byte[][] _bigArray; 

     public BigObject() 
     { 
      _myId = _idCnt++; 
      Console.WriteLine("BigObject {0} creating... ", _myId); 

      _bigArray = new byte[700][]; 

      for (int i = 0; i < 700; i++) 
      { 
       _bigArray[i] = new byte[OneMeg]; 
      } 

      for (int j = 0; j < 700; j++) 
      { 
       for (int i = 0; i < OneMeg; i++) 
       { 
        _bigArray[j][i] = (byte)i; 
       } 
      } 
      Console.WriteLine("done"); 
     } 

     ~BigObject() 
     { 
      Console.WriteLine("BigObject {0} finalised", _myId); 
     } 
    } 
} 

Mam klasy, BigObject, który tworzy tablicę 700MiB w jej konstruktora i ma metodę sfinalizować który robi nic innego niż druk na konsoli. W opcji Główne tworzę dwa z tych obiektów, jeden wolny, a następnie trzeci.

Jeśli zostanie skompilowany dla 32 bitów (w celu ograniczenia pamięci do 2 gigów), wyjątek braku pamięci zostanie zgłoszony podczas tworzenia trzeciego obiektu BigObject. Dzieje się tak dlatego, że gdy pamięć jest żądana po raz trzeci, żądanie nie może zostać spełnione, a zatem moduł do zbierania śmieci działa. Jednak pierwszy BigObject, który jest gotowy do zebrania, ma metodę finalizatora, więc zamiast być zbierany jest umieszczany w kolejce finalizacji i jest finalizowany. Śmieciarz zatrzymuje się, a wyjątek zostaje rzucony. Jeśli jednak odwołanie do GC.Collect zostanie odkomentowane lub metoda finalizacji zostanie usunięta, kod będzie działał poprawnie.

Moje pytanie brzmi: dlaczego niszczarka śmieci nie robi wszystkiego, aby spełnić prośbę o pamięć? Jeśli uruchomił się dwa razy (raz do sfinalizowania i ponownie do uwolnienia) powyższy kod działałby dobrze. Czy nie powinien on kontynuować gromadzenia śmieci i zbierać, dopóki nie będzie można zwolnić pamięci przed wyrzuceniem wyjątku, i czy istnieje sposób, aby skonfigurować to zachowanie tak (w kodzie lub poprzez Visual Studio)?

+1

Problem spowodowany efektem ubocznym w finalizatora. Nie rób tego! – leppie

+2

Natknąłem się na to kilka lat temu i napisałem post na blogu. Zobacz: http://xacc.wordpress.com/2011/02/22/gc-suppressfinalize/ (zobacz także komentarze) – leppie

+0

Interesujące, ale kod nadal nie powiedzie się, jeśli Konsola.WriteLine zostanie usunięta z finalizatora. –

Odpowiedz

0

Domyślam się, ponieważ czas wykonania finalizatora podczas czyszczenia pamięci jest niezdefiniowany. Nie można zagwarantować, że zasoby zostaną wydane w określonym czasie (chyba że wywołamy metodę Close lub metodę Dispose). Kolejność, w której uruchamiane są finalizatory, jest losowa, więc możesz mieć finalizator na innym obiekcie czekającym, podczas gdy twój obiekt czeka na to .

2

Jego nieokreśloność, gdy GC zadziała i spróbuje odzyskać pamięć.

Jeśli dodasz tę linię po big1 = null. Jednak powinieneś być ostrożny w zmuszaniu GC do zbierania. Nie jest to zalecane, chyba że wiesz, co robisz.

GC.Collect(); 
GC.WaitForPendingFinalizers(); 

Best Practice for Forcing Garbage Collection in C#

When should I use GC.SuppressFinalize()?

Garbage collection in .NET (generations)

+0

Rozumiem, że jest to zła praktyka, jest to tylko aplikacja demonstracyjna do zademonstrowania problemu. Pytanie brzmi: dlaczego odśmiecacz nie uruchamia finalizatorów i wolnej pamięci (co pozwoliłoby na dokonanie alokacji) przed uzyskaniem wyjątku? –

+0

@SeanReid http://stackoverflow.com/questions/10016541/garbage-collection-not-happening-even-when-ededed this. Teraz powinno być oczywiste, dlaczego GC nigdy nie stało się automatycznie. Tworzysz obiekty tylko na LOH (pamiętaj, że typy int, sposób, w jaki je wykorzystałeś, są przydzielane na stosie i nie muszą być zbierane). Nigdy nie wypełniasz generowania 0, więc GC nigdy się nie dzieje. – adt

+0

Chodzi o to, że GC działa, a także działa finalizator obiektu wolnego. (Zauważ, że aplikacja ma 32 bity, więc mamy pewność, że zabraknie nam pamięci przy 2 GB, zamiast zaczynać od strony dla pamięci). Problem polega na tym, że GC musi działać dwa razy, raz, aby uruchomić finalizator i ponownie, aby faktycznie zwolnić pamięć. –

Powiązane problemy