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().