Zasoby niezarządzane, optymalizacja GC

GC nic nie wie o zasobach niezarządzanych. Nie wie ile pamięci one zajmują oraz oczywiście nie jest w stanie zwolnić takich zasobów. O zarządzaniu taką pamięcią pisałem już wiele razy. Opisywałem również zasadę działania GC. Zwykle jest on odpalany po przekroczeniu pewnego progu zużycia pamięci. Niestety, jak wspomniałem, GC nie wie nic o niezarządzanych zasobach. Co w przypadku gdy wrapper zużywa bardzo mało pamięci a zasoby niezarządzane w nim konsumują bardzo wiele pamięci? Dzięki metodom AddMemoryPressure i RemoveMemoryPressure, możemy powiedzieć o tym GC, tak więc wcześniej zostanie on uruchomiony zwalniając wspomniane wrappery.

public static void AddMemoryPressure(Int64 bytesAllocated);
public static void RemoveMemoryPressure(Int64 bytesAllocated);

Warto wywoływać powyższe metody, gdy chcemy dać dodatkową wskazówkę GC.  Przykład:

class BitmapWrapper
{
   private readonly Bitmap _bitmap;
   private readonly Int64 _memoryPressure;

   public BitmapWrapper(String file, Int64 size)
   {
       _bitmap = new Bitmap(file);
       if (_bitmap != null)
       {
           _memoryPressure = size;
           GC.AddMemoryPressure(_memoryPressure);
       }
   }

   public Bitmap GetBitmap()
   {
       return _bitmap;
   }

   ~BitmapWrapper()
   {
       if (_bitmap != null)
       {
           _bitmap.Dispose();
           GC.RemoveMemoryPressure(_memoryPressure);
       }
   }
}

Bitmapa jest dobrym przykładem bo zwykle wrapper zajmuje mało a zasoby niezarządzane mogą pochłaniać dużo pamięci, w zależności od rozmiaru bitmapy. Może zatem zdarzyć się, że GC będzie trzymał bardzo dużą liczbę takich obiektów, bo z jego punktu widzenia nie stanowią one obciążenia dla pamięci.

Podobną rolę pełni klasa HandleCollector:

public sealed class HandleCollector 
{

    public HandleCollector(String name, Int32 initialThreshold);
    public HandleCollector(String name, Int32 initialThreshold, Int32 maximumThreshold);
    public void Add();
    public void Remove();
    public Int32 Count { get; }
    public Int32 InitialThreshold { get; }
    public Int32 MaximumThreshold { get; }
    public String Name { get; }

}

W konstruktorze podajemy nazwę zasobu (własną) oraz ilość dozwolonych instancji, jakie powinny być trzymane w pamięci. Pewne zasoby niezarządzane mają limitowaną ilość – tzn. można stworzyć np. maksymalnie 5 uchwytów do nich. W celu stworzenia kolejnych, należy najpierw zwolnić poprzednie. Dzięki HandleCollector, możemy dać GC instrukcję, że po przekroczeniu danego limitu, GC powinien zostać uruchomiony w celu usunięcia niepotrzebnych wrapperów na niezarządzane uchwyty. HandleCollector ma wewnętrzny licznik, którym można zarządzać za pomocą Add oraz Remove. Przykład:

internal class LimitedResource
{
   private static readonly HandleCollector _handleCollector = new HandleCollector("LimitedResource", 5);

   public LimitedResource()
   {
       _handleCollector.Add();
   }
   ~LimitedResource()
   {
       _handleCollector.Remove();
   }
}

Oba rozwiązania wewnętrznie tak naprawdę wywołują GC.Collect().

Leave a Reply

Your email address will not be published.