Brak pamięci i tworzenie obiektów

0

Kiedyś pisałem taki program, który tworzył duże obiekty, które były krótko potrzebne (przewidywany czas życia obiektu to jeden przebieg pętli). Abstrahując od tego, jakie to były obiekty i do czego były one potrzebne (przerabiałem to z tablicami i bitmapami) spotkałem się z następującym problemem. Załóżmy, że jest taki kod:

while (Working)
{
    JakasKlasa Obj = new JakasKlasa();
    Obj.JakasOperacja();
}

W każdym przebiegu pętli tworzony jest obiekt o podobnym rozmiarze w pamięci. Mówi się, że zaletą .NET jest brak konieczności ręcznego zwalniania pamięci, ale okazuje się, że GC coś nie bardzo działa, bo zdarza się wyjątek "Out of memory" przy próbie tworzenia obiektu po iluś przebiegach pętli, nie ma reguły, po ilu.

Rozszerzam kod o ręczne wywoływanie GC:

while (Working)
{
    System.GC.Collect(System.GC.MaxGeneration, System.GCCollectionMode.Forced);
    JakasKlasa Obj = new JakasKlasa();
    Obj.JakasOperacja();
}

Wtedy problemu nie ma, ale co każdy przebieg jest uruchamiany odśmiecacz.

Według moich przemyśleń, .NET tworzy obiekt według algorytmu narysowanego w załączniku. Takie postępowanie moim zdaniem miałoby sens i nie byłoby problemu braku pamięci.
Obiekt.GIF

Dlaczego w praktyce nie zawsze jest uruchamiany GC, tylko jest wywalany "Out of memory" bez próby oczyszczenia pamięci?

Podobny problem spotkałem w Javie, ale nie udało mi się rozwiązać, zarzuciłem projekt. Podobno nie ma możliwości uruchomienia odśmiecacza, a wywołanie system.gc() nie gwarantuje uruchomienia odśmiecacza.

Ciekaw jestem też, dlaczego .NET i Java nie przewidują ręcznego niszczenia obiektów podobnie, jak w C++. Wydaje mi się, że wprowadzenie automatycznego odśmiecacza nie przeszkadza w mozliwości ręcznego niszczenia obiektu.

Np. mogłoby być tak:

while (Working)
{
    JakasKlasa Obj = new JakasKlasa();
    Obj.JakasOperacja();
    delete Obj;
}

Wtedy nie byłoby ani pełnego odśmiecania, ani problemów z brakiem pamieci.

Oczywistym jest, że warto przemyśleć algorytm, czy da się to inaczej bez częstego tworzenia i niszczenia obiektów, ale mi chodzi o przyczynę przedstawionych problemów.

0

Nie wiem czy unsafe mode nie pozwala używać destruktorów.

1

Ja bym to zrobił inaczej. Utworzyłbym pulę obiektów i z niej korzystał. Może to być lista w której obiekty nie będą niszczone, ale oznaczane jako do ponownego użytku. Oczywiście też metoda czyszcząca się nada. Mogą też być dwie listy - jedna posiadająca obiekty do użytku, a druga wykorzystywane. Wzrośnie wydajność aplikacji, bo odejdzie ciągła rezerwacja pamięci i odciąży to znacznie GC.

Nie podałeś szczegółów aplikacji, ale jeżeli jest tak jak to widać - czyli jedna pętla w jednowątkowej aplikacji, to wystarczy tobie jeden reużywany obiekt.

1
andrzejlisek napisał(a):

Dlaczego w praktyce nie zawsze jest uruchamiany GC, tylko jest wywalany "Out of memory" bez próby oczyszczenia pamięci?

Bo to nie działa tak, jak narysowałeś, w sensie tworzenie obiektów i sprzątanie są od siebie dość niezależne.
GC czasami się włącza, sprząta zgodnie ze swoim algorytmem i ustawieniami. A OutOfMemoryException jest rzucany wtedy, gdy system operacyjny stwierdzi, że brakuje pamięci dla procesu.

Wtedy nie byłoby ani pełnego odśmiecania, ani problemów z brakiem pamieci.

Byłyby, tak jak są w C++, gdy zapomni się o delete.

Oczywistym jest, że warto przemyśleć algorytm, czy da się to inaczej bez częstego tworzenia i niszczenia obiektów, ale mi chodzi o przyczynę przedstawionych problemów.

Jeśli Twoje obiekty są duże (powyżej 85 tys bajtów), to są tworzone w specjalnym miejscu pamięci, zwanym Large Objects Heap, w którym obiekty są przypięte i nie zmieniają swojego położenia, a na dodatek od razu lądują w generacji 2 Garbage Collectora. Generacja ta jest najrzadziej czyszczona.

Możesz spróbować pobawić się z ustawieniami GCLatencyMode albo GCLargeObjectHeapCompactionMode, ale to nie musi pomóc.

0

Abstrahując od tego, jakie to były obiekty

Ale to jest dość istotne, jakie to były obiekty.
Czy jest to klasa, która implementuje IDisposable? Jeśli tak, to wystarczy dodać wywołanie Dispose.

A jeśli nie, jeśli to twoja własna klasa, to może trzeba się zainteresować tym Dispose, a nie mieszać z GC od razu.

0

Skorojest są istotne typy obiektów, to w jednym przypadku był to obiekt "System.Drawing.Bitmap".

Pisałem program do pewnej obróbki serii zdjęć. Nie rozpisując się, w dużym uproszczeniu można napisać, że działanie jest bardzo podobne do przeglądarki zdjęć wbudowanej w system Windows, wzbogacone o różne funkcjonalności. W C++ nie byłoby problemu, bo wczytuję obraz do wyświetlenia, a jak zmieniam obraz, to robię delete na obiekcie obrazu i wczytuje obraz z następnego pliku. A w C# jedyny sposób, jaki mi przyszedł do głowy, to odczyt obrazu z pliku i w momencie przełączenia plików muszę porzucić obiekt (zgubić referencję) i do tego pola utworzyć nowy obiekt powstały z następnego pliku. Jak testowałem ten program, to zdarzały się sytuacje błędu "Out of memory". Pliki to były BMP o wielkości ok. 11MB (strona A4 zeskanowana w 200DPI) Jak zrobiłem uruchomienie GC na początku procedury wczytującej plik, to program działał poprawnie bez żadnego problemu. Przed napisaniem tego posta zakomentowałem uruchamianie GC i program ani razu się nie wywalił, ale przy szybkim przełączaniu plików w menedżerze zadań przybywała ilość używanej pamięci do ponad 1GB, a komputer zaczynał wolniej działać i program się przywieszał, wtedy pamięć znacznie spadała w wszystko wracało do normy. Jak odkomentowałem uzycie GC, to w menedżerze zadań podczas przełączania stron użycie pamięci utrzymywało się na stałym poziomie kilkadziesiąt MB i program działał idealnie bez zacinania się spełniając swoją funkcję.

Czy Dispose obiektu (o ile implementuje IDisposable) niszczy obiekt i można traktować jako odpowiednik delete w C++? Jeżeli nie, to Dispose chyba i tak nie rozwiąże problemu. Albo, jak się korzysta z using, to czy obiekt znika zaraz po wyjściu z bloku using, czy obiekt jest gubiony i czeka na zmiecenie przez GC?

Innym przypadkiem był program wczytujący obiekty własnej klasy zawierającej drzewa i tablice, jeden obiekt miał kilkaset MB (program był uruchamiany na serwerze mającym chyba 16GB RAM), ciężko było wielokrotnie modyfikować i używać ten sam obiekt, wiec zdałem sie na alokowanie i niszczenie. Tam program co rusz się wywalał, dodanie uruchamiania GC do pętli rozwiązało problem.

Ciekawe, dlaczego w .NET nie ma odśmiecania poprzez zliczanie referencji. Zliczanie w większości przypadków obiekt by się zniszczył natychmiast po zgubieniu referencji, a GC byłby potrzebny do zmazania obiektów, które się odwołują do siebie, ale nie ma do nich dostepu z poziomu programu.

0
andrzejlisek napisał(a):

Czy Dispose obiektu (o ile implementuje IDisposable) niszczy obiekt i można traktować jako odpowiednik delete w C++?

Dispose służy do zwalniania zasobów skojarzonych z obiektem. W przypadku Bitmapjest to pamięć.

Jeżeli nie, to Dispose chyba i tak nie rozwiąże problemu.

Sprawdziłeś, czy tak sobie gdybasz?

Albo, jak się korzysta z using, to czy obiekt znika zaraz po wyjściu z bloku using, czy obiekt jest gubiony i czeka na zmiecenie przez GC?

Using to eleganckie i bezpieczne wywołanie Dispose.

1 użytkowników online, w tym zalogowanych: 0, gości: 1