2009-10-10 12 views
11

Opracowujemy dość dużą aplikację Windows Forms. Na komputerach kilku klientów często zawiesza się z wyjątkiem OutOfMemory. Po uzyskaniu pełnego zrzutu pamięci aplikacji po wystąpieniu wyjątku (clrdump wywołany z obsługi UnhandledException) przeanalizowałem go za pomocą ".NET Memory Profiler" i windbg.OutOfMemory, ale bez gcroots dla wielu obiektów

Profiler pamięci pokazał tylko 130 MB w instancjach obiektów na żywo. Co ciekawe, dla wielu typów obiektów pokazano bardzo dużą liczbę nieosiągalnych instancji (np. 22000 nieosiągalnych instancji Byte []). W statystykach pamięci rodzimej wartość ta wynosi 127 MB we wszystkich stertach dla danych (co jest w porządku), ale wskazuje nieosiągalne 133 MB w sterty nr 2 i 640 MB w dużej sterty (nie jest dobrze!).

Analizując zrzut z WinDbg, powyższe statystyki są potwierdzone:

!dumpheap -stat 
..... acceptable object sizes... 
79330a00 467216  30638712 System.String 
0016d488  4804 221756612  Free 
79333470 27089 574278304 System.Byte[] 

Aplikacja robi użyć dużej liczby krótkich buforów poprzez swój bieg czasu, ale ich nie przeciekać. Testowanie wielu instancji Byte [] przy pomocy! Gcroot kończy się bez korzeni. Oczywiście większość z tych tablic jest nieosiągalna, jak wskazuje profiler pamięci.

Wystarczy, aby upewnić się, wszystko jest w porządku,! Finalizequeue pokazuje żadne przedmioty czekają zostać sfinalizowane

generation 0 has 138 finalizable objects (18bd1938->18bd1b60) 
generation 1 has 182 finalizable objects (18bd1660->18bd1938) 
generation 2 has 75372 finalizable objects (18b87cb0->18bd1660) 
Ready for finalization 0 objects (18bd1b60->18bd1b60) 

a także sprawdzić na rodzimym śladu wątek finalizator stosu pokazuje, że nie jest zablokowany.

W tej chwili nie, jak zdiagnozować dlaczego GC nie zbiera dane (i wierzę, że to lubią, ponieważ proces zabrakło pamięci ..)

edit: Based na wejściu poniżej czytałem trochę więcej na temat rozdrobnienia dużych obiektów i wydaje się, że tak być może.

Widziałem kilka rad, aby przydzielić większe bloki pamięci dla tego rodzaju danych (różne bajty [] w moim przypadku) i zarządzać pamięcią w tym obszarze przeze mnie, ale wydaje się to raczej raczej hackowskim rozwiązaniem, a nie jeden z nich spodziewałbym się rozwiązać problem z niezbyt specjalną aplikacją komputerową.

Problem fragmentacji spowodowany jest faktem (Przynajmniej tyle osób z Microsoftu pisze na blogach), że przedmioty na LOH nie są przenoszone w czasie istnienia, co jest zrozumiałe, ale wydaje się logiczne, że gdy pewne ciśnienie pamięci jest osiągnięte, takie jak groźba uzyskania OOM, należy przeprowadzić przeniesienie.

Jedyne, co mnie martwi, zanim w pełni ufa, że ​​przyczyną jest fragmentacja, jest to, że tak wiele obiektów na LOH jest bez odniesień gcroot - czy to dlatego, że nawet w przypadku zbierania śmieci LOH wykonywane są tylko częściowo?

Z przyjemnością wskażę mi jakieś ciekawe rozwiązanie, ponieważ w tej chwili jedyne, co znam, to niestandardowe zarządzanie niektórymi blokami pamięci.

Wszelkie pomysły są mile widziane. Dzięki.

Odpowiedz

2

Jak zwykle rzeczy okazały się trochę inne. Znaleźliśmy przypadek, w którym aplikacja zużywała dużo pamięci i ostatecznie przechodziła do OOM. Co było dziwnego w wysypiskach, które znaleźliśmy, zanim odkryliśmy, że było dużo przedmiotów bez gcroota - nie rozumiałem, dlaczego nie zostało ono uwolnione i użyte do nowych przydziałów? Wtedy doszedłem do wniosku, że to, co prawdopodobnie się wydarzyło, gdy doszło do OOM - stos został rozwinięty, a przedmioty, które były właścicielami pamięci, nie były już dostępne, a następnie wykonano zrzut. Dlatego wydawało się, że dużo pamięci może być GCed.

Co zrobiłem w wersji debug - aby pobrać prawdziwe state-of-the-zrzut pamięci - to stworzył Threading.Timer, który sprawdza, czy niektóre dość duży obiekt może zostać przydzielone - jeśli nie może być przydzielone, oznacza to, że jesteśmy blisko OOM i że jest to dobry czas na zrzucenie pamięci. Kod następująco:

private static void OomWatchDog(object obj) 
{ 
try       
{ 
    using(System.Runtime.MemoryFailPoint memFailPoint = 
      new System.Runtime.MemoryFailPoint(20)) 
    { 
    } 
} 
catch (InsufficientMemoryException) 
{ 
    PerformDump(); 
} 
} 
4

LOH podlega fragmentacji. This article zapewnia analizę i podstawowe wskazówki dotyczące obejścia tego problemu.
Może mógłbyś opublikować kod pokazujący "typowe" użycie tych buforów bajtów []?

+0

Bufory różnią się od 20k do kilku MB i zwykle są tworzone przez dostawcę bazy danych, aby załadować dane do postaci db lub nasz kod, który następnie wypełnia je danymi z gniazda (rozmiar danych jest znany, więc definiowane są bufory z prawidłowym rozmiarem, nie rosną) – grepfruit

+0

Czy są znane wielkości w czasie wykonywania lub designtime? Wygląda na to, że będziesz musiał znaleźć sposób na ich ponowne wykorzystanie. –

+0

Rozmiar jest ustalany w czasie wykonywania - elementy pamięci MIME w buforze przechowują wiadomości e-mail, więc każdy bufor różni się rozmiarem. Możemy próbować ponownie użyć naszych własnych buforów, ale są też bufory od dostawcy. Ale tak, w tej chwili będziemy musieli spróbować ponownie wykorzystać część bufora. Chociaż to tylko opóźni problem, uważam .. – grepfruit

1

Czasami Image.FromFile ("plik inny niż obraz") generuje wyjątek OutOfMemoryException. Plik zerowy to jeden taki plik, który będzie.

+0

Dzięki za informacje, doświadczyliśmy tego problemu przedtem, więc dobry punkt. Jednak w tym momencie OOM jest rzucane podczas alokacji, więc prawdopodobnie nie ma wystarczająco dużo miejsca ciągłego. – grepfruit

1

Jeśli uważasz LOH jest problem wtedy o punkt załamania na przydział LOH mógłby wskazać we właściwym kierunku. Prawdopodobnie możesz zrobić coś podobnego do tego:

bp mscorwks! Gc_heap :: allocate_large_object "! Clrstack; .echo ********* Przydział sterty dużych obiektów ***********; g "