Wystąpił problem z obiektami finalizowalnymi, które nie zostały zebrane przez GC
, jeśli nie zostały jawnie wywołane. Wiem, że powinienem jawnie nazwać Dispose()
, jeśli obiekt implementuje IDisposable
, ale zawsze uważałem, że można bezpiecznie polegać na strukturze, a gdy obiekt staje się bez odniesienia, można go zebrać.Dlaczego obiekt nie jest zbierany z finalizerem, nawet jeśli nie jest unieważniony?
Ale po kilku eksperymentach z WinDbg/SOS/sosex Odkryłam, że jeśli GC.SuppressFinalize() nie został wezwany do finalizable obiektu nie zbiera się, nawet jeśli staje Nieukorzenione. Tak więc, jeśli intensywnie korzystasz z obiektów finalizowalnych (DbConnection, FileStream itp.) I nie wyrzucasz ich jawnie, możesz napotkać zbyt duże zużycie pamięci lub nawet OutOfMemoryException
.
Oto przykładowa aplikacja:
public class MemoryTest
{
private HundredMegabyte hundred;
public void Run()
{
Console.WriteLine("ready to attach");
for (var i = 0; i < 100; i++)
{
Console.WriteLine("iteration #{0}", i + 1);
hundred = new HundredMegabyte();
Console.WriteLine("{0} object was initialized", hundred);
Console.ReadKey();
//hundred.Dispose();
hundred = null;
}
}
static void Main()
{
var test = new MemoryTest();
test.Run();
}
}
public class HundredMegabyte : IDisposable
{
private readonly Megabyte[] megabytes = new Megabyte[100];
public HundredMegabyte()
{
for (var i = 0; i < megabytes.Length; i++)
{
megabytes[i] = new Megabyte();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~HundredMegabyte()
{
Dispose(false);
}
private void Dispose(bool disposing)
{
}
public override string ToString()
{
return String.Format("{0}MB", megabytes.Length);
}
}
public class Megabyte
{
private readonly Kilobyte[] kilobytes = new Kilobyte[1024];
public Megabyte()
{
for (var i = 0; i < kilobytes.Length; i++)
{
kilobytes[i] = new Kilobyte();
}
}
}
public class Kilobyte
{
private byte[] bytes = new byte[1024];
}
Nawet po 10 powtórzeń może się okazać, że zużycie pamięci jest zbyt wysoka (od 700MB do 1GB) i staje się jeszcze większa z większą liczbą powtórzeń. Po dołączeniu do procesu za pomocą programu WinDBG można stwierdzić, że wszystkie duże obiekty są unieważnione, ale nie zostały zebrane.
Sytuacja zmienia się, jeśli jednoznacznie nazwiesz SuppressFinalize()
: zużycie pamięci jest stabilne około 300-400 MB nawet pod wysokim ciśnieniem, a WinDBG pokazuje, że nie ma żadnych nie unieważnionych obiektów, pamięć jest darmowa.
Pytanie brzmi: czy jest to błąd w strukturze? Czy istnieje jakieś logiczne wytłumaczenie?
Więcej szczegółów:
Po każdej iteracji, windbg pokazuje, że:
- kolejka zakończenie jest pusty
- freachable kolejki jest pusty
- generacja 2 zawiera obiekty (stu) z poprzednich iteracji
- obiekty z poprzednich iteracji są wycofane z eksploatacji
Próbowałem go uruchomić i dostałem go do 700mb, w którym to momencie obniżył się do 100mb lub tak na własną rękę. (Właśnie naciskałem return i obserwowałem użycie pamięci w Menedżerze zadań). System Windows 8. – keyboardP
Bardzo interesujące ... testowałem go na win7 x64. Pierwsze 10 iteracji z jedną sekundową pauzą, po tym po prostu przytrzymaj dowolny klucz ... outofmemory – 6opuc
Próbowałem ponownie, ale dostałem 100 iteracji bez wyjątku. Również 64-bitowy, ale całkiem interesujący. Może ktoś inny może również testować i publikować wyniki. – keyboardP