## 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

1. Technikalia
   1. Stack
   2. Small Object Heap i Large Object Heap
   3. Dispose i Finalizer
2. Garbage Collector
   1. Jak działa?
   2. Kiedy ruszy?
   3. Alokacje bez GC?
3. Podsumowanie

## Impuls

## 1. Technikalia

## 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
    - Execution Engine
    - JiT
- 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.

## Stack (stos)

## 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

## 2. Garbage Collector

## 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.

## Dziękuję za uwagę