Pamięć w .NET
czyli nie po to kodzę w C# by się tym przejmować
Przejmuje się tym Grzesiek Siemoniak
Grzesiek Siemoniak
programista .NET w PGS Software
Agenda
Technikalia
Stack
Small Object Heap i Large Object Heap
Dispose i Finalizer
Garbage Collector
Jak działa?
Kiedy ruszy?
Alokacje bez GC?
Podsumowanie
Pamięć w .NET
Kilka stert
process heap
small object heap (SOH)
large object heap (LOH)
loader heap
high frequency heap
low frequency heap
stub heap
code heap
czyszczona automatycznie przez Garbage Collector
small object heap
large object heap
1. Small object heap i large object heap
2. Loader heap - tutaj między innymi trafiają wartości stałe oraz statyczne, poza GC
3. process heap - pamięc niezarządzalna, natywna
code heap - pamięc wykrozystywana przez Execution Engine i JIT
4. (Nie mam bezpośredniego dostępu do pamięci fizycznej)
Stack (stos)
jeden stos per wątek
miejsce na zmienne lokalne oraz argumenty funkcji
wywołanie metody tworzy kontener (tzw. stack frame)
gdy metoda kończy się, kontener jest usuwany
1. Stos = stan aplikacji
2. metoda = stack frame
3. Zakończenie metody usuwa frame i stan wraca do poprzedniej metody
4. Wątek końćzy się gdy nie ma więcej framów.
Value Type
byte, int, long, char, itd.
struct
IntPtr
Warto wiedzieć
zmienne statyczne są zawsze umieszczane na stercie, niezależnie od typu
1. Zmienne żyją tam gdzie zostały zadeklarowane. Wewnątrz metody, wtedy na stosie, w klasie to na stercie.
2. Przekazywane jako kopie (kopia wskaźnika). Można użyć słowa kluczowego ref, przkekazujemy referencję
3. strukty nie powinny przekraczać 16 bytes (kopiowanie kosztowne)
4. http://www.developerfusion.com/article/4705/memory-in-net-what-goes-where/2/
Small Object Heap
obiekty zawsze umieszczone jeden po drugim
Next Object Pointer wskazuje na miejsce następnej alokacji
nowy obiekt trafi zawsze na koniec sterty
dotychczasowy adres lokalizacji obiektu może ulec zmianie, chyba że został przypięty
1. najwięcej alokacji odbywa się tutaj
2. alokacje są szybkie (nie trzeba szukać wolnego miejsca i wiadomo gdzie go umieścić)
3. obiekty są przenoszone by nie było wolnych miejsc między nimi
4. Przypięcie: fixed (int* a = &person.age), GCHandle.AddrOfPinnedObject(); = fragmentacja
5. Pinning an object prevents it from being moved by the garbage collector. In the generational model, it prevents promotion of pinned objects between generations. This is especially significant in the younger generations, such as generation 0, because the size of generation 0 is very small. Pinned objects that cause fragmentation within generation 0 have the potential of causing more harm than it might appear from examining pinned before we introduced generations into the the picture. Fortunatly, the CLR has the ability to promote pinned objects using the following trick: if generation 0 becomes severely fragmented with pinned objects, the CLR can declare the entire space of generation 0 to be considered a higher generation and allocate new objects from a new region of memory that will become generation 0. This is achieved by changing the ephemeral segment.
Boxing i unboxing
boxing - ze stosu na stertę, kosztowne, obciąża GC
unboxing - ze sterty na stos, o wiele tańsze
Warto wiedzieć
konwersja z value type na interfejs skutkuje boxingiem
zmienna może być boxowana kilkukrotnie
1. zmienna ze stosu na stertę i vice versa
2. boxing = pamięc do zwolnienia przez GC
3. struct na interfejs = boxing
4. jedna zmienna może być boxowana kilka razy (string.format("", 1, 1, 1))
Large object heap
obiekty o rozmiarze 85kB i większe
istnieje wyjątek
tablic double ≥ 1000 elementów
do śledzenia wolnej pamięci służy Free Space Table
fragmentowana - po jakimś czasie zawsze będą niezapełnialne "dziury", ale można to zmienić wywołując GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce
1. zwolnenie pamięci tylko przy pełnej kolekcji (gen 2)
2. free space table trzyma informację o wolnych przestrzenaich między obiektami
3. obiekty żyją długo nawet gdy są nie potrzebne
4. fragmentacja bardzo kosztowna
5. zwolniona pamięć może nigdy nie zostać użyta ponownie
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
Dispose
zwalnia zasoby niezarządzalne
deterministyczny - zwolnienie następuje w czasie wywołania
1. jeśli zasób implementuje dispose to powinien być na końcu wywołany
2. zwolnienie następuje w czasie wywołania
Finalizer
przydatny tylko do zwolnienia niezarządzalnej pamięci
dodatkowo obciąża Garbage Collectora
jeśli wystąpi w nim błąd, to cały proces zostanie zakończony
1. użyteczny w wyjątkowych sytuacjach (WaitEventHandle)
2. kosztowny
3. z Dispose GC.SupressFinalize()
4. przydatny w debugowaniu
Część 1 podsumowanie
Stos - zmienne lokalne, krótko żyjące
2 sterty:
small object heap
large object heap
zasoby niezarządzalne - pamiętajmy o Dispose
Garbage Collector - co to
odpowiedzialny za usuwanie ze sterty obiektów niepotrzebnych
obiekty dzielone na generacje: gen 0, gen 1 i gen 2
generacja 0 sprawdzana najczęściej, generacja 2 najrzadziej
obiekty mogą żyć dłużej niż jest to potrzebne
LOH sprawdzany tylko podczas czyszczenia generacji 2
dwa tryby działania
1. odpowiada za zwolnienie pamięci (sam jest w stanie określić co jest już niepotrzebne)
2. zoptymalizowany
3. uruchomienie powoduje wstrzymanie aplikacji
4. możliwość skonfigurowania
GC - kiedy ruszy
kiedy generacja osiągnie rozmiar:
gen 0 dojdzie do +- 256KB
gen 1 dojdzie do +- 2MB
gen 2 dojdzie do +- 10MB
zostanie wywołane GC.Collect()
system operacyjny wyśle powiadomienie o niskiej pamięci
1. gen 0 obiekty nowe i żyjące krótko, gen 2 obiekty długowieczne
2. dopasowuje limity po pewnym czasie
3. gen 0 jest sprawdzana najczęściej (1:10:100)
4. sprawdzenie od gen 2 nazywa sie full collection
5. nie powinien być wywoływany samodzielnie
Jak działa GC
Mark - oznaczanie obiektów "żywych"
Sweep - usunięcie obiektów już niepotrzebnych
Compact - przesunięcie obiektów w SOH
1. Workstation - największa responsywność UI, Server przepustowaść requestów
2. Works GC używa jedngo wątku (stara wersja bez wątku w tle) + możliwy wątek w tle
3. GC używa wszystkich wątków - tworzy SOH i LOH per procesor logiczny
4. Tryb by aplikacja była jak najbardziej responsywna
5. Batch - Tryb by pamięc była czyszczona często, LowLatency - minimum, Interactive
Sterowanie GC
Latency
Batch
LowLatency
Interactive
NoGCRegion
SustainedLowLatency
1. GC.TryStartNoGCRegion(4096, true)
2. GC.EndNoGCRegion();
3. https://msdn.microsoft.com/pl-pl/library/system.runtime.gclatencymode(v=vs.110).aspx
No GC Region
try
{
GC.TryStartNoGCRegion(TOTAL_SIZE, true);
--reszta kodu
}
finally
{
if (GCSettings.LatencyMode == GCLatencyMode.NoGCRegion) GC.EndNoGCRegion();
}
1. GC.TryStartNoGCRegion(4096, true)
2. GC.EndNoGCRegion();
3. https://msdn.microsoft.com/pl-pl/library/system.runtime.gclatencymode(v=vs.110).aspx
Część 2 podsumowanie
tylko GC może usunąć "śmieci" z pamięci zarządzalnej
autonomiczny byt, który odgrywa ważna rolę w naszej aplikacji
GC jest dobry w tym co robi, ale warto mu tę pracę ułatwić
Szczęśliwy koniec historii?
Szczęśliwy koniec historii!
Nie używajmy zmiennych statycznych tam, gdzie nie jest to potrzebne.
Podsumowanie całości
alokacje nie są kosztowne
czy obiekt trafi na SOH, czy LOH ma znaczenie
pamiętajmy o Dispose
czyszczenie generacji 0 i 1 jest szybkie
czyszczenie generacji 2 takie szybkie nie jest, więc nie przedłużajmy życia obiektu, jeśli nie jest to konieczne
Milo Minderbinder - i wszyscy mają udział w zyskach.