Odzyskiwanie pamięci

Platforma Xamarin.Android używa prostego modułu odśmiecającego pamięci mono. Jest to moduł odśmiecający śmieci z dwiema generacjami i dużą przestrzenią obiektu, z dwoma rodzajami kolekcji:

  • Kolekcje pomocnicze (zbiera stertę Gen0)
  • Główne kolekcje (zbiera 1. generację i duże sterty przestrzeni obiektów).

Uwaga

W przypadku braku jawnej kolekcji za pośrednictwem GC. Kolekcje Collect()na żądanie oparte na alokacjach sterty. Nie jest to system zliczania odwołań; obiekty nie będą zbierane natychmiast, gdy nie ma zaległych odwołań lub gdy zakres zakończył się. GC zostanie uruchomiona, gdy pomocnicze sterty zabraknie pamięci dla nowych alokacji. Jeśli nie ma alokacji, nie zostanie uruchomiona.

Kolekcje pomocnicze są tanie i częste i są używane do zbierania ostatnio przydzielonych i utraconych obiektów. Kolekcje pomocnicze są wykonywane po kilku MB przydzielonych obiektów. Kolekcje pomocnicze mogą być wykonywane ręcznie przez wywołanie usługi GC. Zbieranie (0)

Główne kolekcje są kosztowne i rzadziej używane do odzyskiwania wszystkich martwych obiektów. Kolekcje główne są wykonywane po wyczerpaniu pamięci dla bieżącego rozmiaru sterty (przed zmianą rozmiaru sterty). Główne kolekcje mogą być wykonywane ręcznie przez wywołanie usługi GC. Zbierz () lub wywołując GC. Zbierz (int) za pomocą argumentu GC. MaxGeneration.

Kolekcje obiektów między maszynami wirtualnymi

Istnieją trzy kategorie typów obiektów.

  • Obiekty zarządzane: typy, które nie dziedziczą z obiektu Java.Lang.Object , np. System.String. Są one zbierane zwykle przez GC.

  • Obiekty Java: typy języka Java, które znajdują się na maszynie wirtualnej środowiska uruchomieniowego systemu Android, ale nie są widoczne dla maszyny wirtualnej Mono. Są one nudne i nie zostaną omówione dalej. Są one zwykle zbierane przez maszynę wirtualną środowiska uruchomieniowego systemu Android.

  • Obiekty równorzędne: typy, które implementują klasy IJavaObject , np. wszystkie podklasy Java.Lang.Object i Java.Lang.Throwable . Wystąpienia tych typów mają dwie "połowy" zarządzanego elementu równorzędnego i natywnego elementu równorzędnego. Zarządzana komunikacja równorzędna jest wystąpieniem klasy C#. Natywna komunikacja równorzędna jest wystąpieniem klasy Java na maszynie wirtualnej środowiska uruchomieniowego systemu Android, a właściwość IJavaObject.Handle języka C# zawiera globalne odwołanie JNI do natywnego elementu równorzędnego.

Istnieją dwa typy natywnych elementów równorzędnych:

  • Elementy równorzędne platformy : "Normalne" typy języka Java, które nie znają niczego na platformie Xamarin.Android, np. android.content.Context.

  • Komunikacja równorzędna użytkownika: wywoływane otoki systemu Android, które są generowane w czasie kompilacji dla każdej podklasy Java.Lang.Object obecnej w aplikacji.

Ponieważ w procesie platformy Xamarin.Android istnieją dwie maszyny wirtualne, istnieją dwa typy odzyskiwania pamięci:

  • Kolekcje środowiska uruchomieniowego systemu Android
  • Kolekcje mono

Kolekcje środowiska uruchomieniowego systemu Android działają normalnie, ale z zastrzeżeniem: globalne odwołanie JNI jest traktowane jako katalog główny GC. W związku z tym, jeśli istnieje globalne odwołanie JNI do obiektu maszyny wirtualnej środowiska uruchomieniowego systemu Android, nie można zebrać obiektu, nawet jeśli w inny sposób kwalifikuje się do kolekcji.

Kolekcje Mono to miejsce zabawy. Obiekty zarządzane są zbierane normalnie. Obiekty równorzędne są zbierane przez wykonanie następującego procesu:

  1. Wszystkie obiekty równorzędne kwalifikujące się do kolekcji Mono mają globalne odwołanie JNI zastąpione słabym odwołaniem globalnym JNI.

  2. Wywoływana jest maszyna wirtualna środowiska uruchomieniowego systemu Android. Każde wystąpienie natywnej komunikacji równorzędnej może być zbierane.

  3. Sprawdzane są słabe odwołania globalne JNI utworzone w (1). Jeśli zebrano słabe odwołanie, zbierany jest obiekt elementu równorzędnego. Jeśli słabe odwołanie nie zostało zebrane, słabe odwołanie jest zastępowane odwołaniem globalnym JNI, a obiekt elementu równorzędnego nie jest zbierany. Uwaga: w interfejsie API 14 lub nowszym oznacza to, że wartość zwrócona z IJavaObject.Handle może ulec zmianie po GC.

Wynikiem końcowym tego wszystkiego jest to, że wystąpienie obiektu elementu równorzędnego będzie żyć tak długo, jak długo jest przywołyzane przez kod zarządzany (np. przechowywany w zmiennej static ) lub przywołyszane przez kod Java. Ponadto okres istnienia natywnych elementów równorzędnych zostanie przedłużony poza to, co w przeciwnym razie będzie istnieć, ponieważ element równorzędny natywny nie będzie zbieralny, dopóki nie będzie można zbierać zarówno elementu równorzędnego natywnego, jak i zarządzanego elementu równorzędnego.

Cykle obiektów

Obiekty równorzędne są logicznie obecne zarówno w środowisku uruchomieniowym systemu Android, jak i w przypadku maszyn wirtualnych Mono. Na przykład wystąpienie zarządzanej komunikacji równorzędnej Android.App.Activity będzie mieć odpowiednie wystąpienie java elementu równorzędnego android.app.Activity framework. Wszystkie obiekty dziedziczone z obiektu Java.Lang.Object mogą mieć reprezentacje na obu maszynach wirtualnych.

Wszystkie obiekty, które mają reprezentację na obu maszynach wirtualnych, będą miały okresy istnienia, które są rozszerzone w porównaniu z obiektami, które znajdują się tylko w ramach jednej maszyny wirtualnej (na przykład System.Collections.Generic.List<int>). Wywoływanie usługi GC. Funkcja Collect nie musi zbierać tych obiektów, ponieważ kontroler GC platformy Xamarin.Android musi upewnić się, że obiekt nie jest przywołyny przez jedną maszynę wirtualną przed zebraniem.

Aby skrócić okres istnienia obiektu, należy wywołać metodę Java.Lang.Object.Dispose(). Spowoduje to ręczne "zerwanie" połączenia na obiekcie między dwiema maszynami wirtualnymi przez zwolnienie odwołania globalnego, co pozwoli na szybsze zbieranie obiektów.

Kolekcje automatyczne

Począwszy od wersji 4.1.0, platforma Xamarin.Android automatycznie wykonuje pełną GC po przekroczeniu progu gref. Ten próg wynosi 90% znanych maksymalnych grefs dla platformy: 1800 grefs w emulatorze (maksymalnie 2000) i 46800 grefs na sprzęcie (maksymalnie 52000). Uwaga: platforma Xamarin.Android liczy tylko grefs utworzone przez system Android.Runtime.JNIEnv i nie będzie wiedział o żadnych innych grefs utworzonych w procesie. Jest to tylko heurystyczna.

Po wykonaniu automatycznej kolekcji komunikat podobny do następującego zostanie wyświetlony w dzienniku debugowania:

I/monodroid-gc(PID): 46800 outstanding GREFs. Performing a full GC!

Wystąpienie tego elementu jest niedeterministyczne i może wystąpić w nieodpowiednich czasach (np. w trakcie renderowania grafiki). Jeśli widzisz ten komunikat, możesz chcieć wykonać jawną kolekcję w innym miejscu lub spróbować zmniejszyć okres istnienia obiektów równorzędnych.

Opcje mostka GC

Platforma Xamarin.Android oferuje przezroczyste zarządzanie pamięcią za pomocą systemu Android i środowiska uruchomieniowego systemu Android. Jest on implementowany jako rozszerzenie modułu odśmiecającego mono o nazwie Mostek GC.

Mostek GC działa podczas odzyskiwania pamięci Mono i określa, które obiekty równorzędne wymagają ich "liveness" zweryfikowane za pomocą sterta środowiska uruchomieniowego systemu Android. Mostek GC sprawia, że ta determinacja jest następująca (w podanej kolejności):

  1. Wywołaj graf referencyjny mono obiektów równorzędnych, które nie są osiągalne, do obiektów języka Java, które reprezentują.

  2. Wykonaj GC języka Java.

  3. Sprawdź, które obiekty są naprawdę martwe.

Ten skomplikowany proces umożliwia podklasom Java.Lang.Object swobodne odwołowanie się do wszystkich obiektów. Usuwa wszelkie ograniczenia, na których można powiązać obiekty Java z językiem C#. Ze względu na tę złożoność proces mostka może być bardzo kosztowny i może spowodować zauważalne przerwy w aplikacji. Jeśli aplikacja ma znaczące wstrzymanie, warto zbadać jedną z następujących trzech implementacji mostka GC:

  • Tarjan - zupełnie nowy projekt mostu GC oparty na algorytmie Roberta Tarjana i propagacji odwołań wstecznych. Ma najlepszą wydajność w ramach symulowanych obciążeń, ale ma również większy udział w kodzie eksperymentalnym.

  • Nowy — gruntowny przegląd oryginalnego kodu, naprawiając dwa wystąpienia zachowania kwadratowego, ale zachowując algorytm podstawowy (oparty na algorytmie Kosaraju do znajdowania silnie połączonych składników).

  • Stary — oryginalna implementacja (uważana za najbardziej stabilną z trzech). Jest to mostek, którego aplikacja powinna używać, jeśli GC_BRIDGE wstrzymanie jest akceptowalne.

Jedynym sposobem na ustalenie, który most GC działa najlepiej, jest eksperymentowanie w aplikacji i analizowanie danych wyjściowych. Istnieją dwa sposoby zbierania danych na potrzeby testów porównawczych:

  • Włącz rejestrowanie — włącz rejestrowanie (zgodnie z opisem w sekcji Konfiguracja ) dla każdej opcji mostka GC, a następnie przechwyć i porównać dane wyjściowe dziennika z każdego ustawienia. GC Sprawdź komunikaty dla każdej opcji, w szczególności GC_BRIDGE komunikaty. Przerwy do 150 ms dla aplikacji nieinterakcyjnych są tolerowane, ale przerwy powyżej 60 ms dla bardzo interaktywnych aplikacji (takich jak gry) są problemem.

  • Włącz księgowość mostów — księgowość mostu spowoduje wyświetlenie średniego kosztu obiektów wskazywanych przez każdy obiekt zaangażowany w proces mostu. Sortowanie tych informacji według rozmiaru zapewni wskazówki dotyczące tego, co przechowuje największą ilość dodatkowych obiektów.

Ustawieniem domyślnym jest Tarjan. Jeśli znajdziesz regresję, może być konieczne ustawienie tej opcji na Stara. Ponadto możesz użyć bardziej stabilnej opcji Stary , jeśli Tarjan nie generuje poprawy wydajności.

Aby określić, która GC_BRIDGE opcja ma być używana przez aplikację, przekaż bridge-implementation=oldbridge-implementation=new zmienną środowiskową MONO_GC_PARAMS lub bridge-implementation=tarjan do niej. Jest to realizowane przez dodanie nowego pliku do projektu za pomocą akcji Kompilacja .AndroidEnvironment Na przykład:

MONO_GC_PARAMS=bridge-implementation=tarjan

Aby uzyskać więcej informacji, zobacz Konfiguracja.

Pomoc w GC

Istnieje wiele sposobów, aby pomóc GC zmniejszyć użycie pamięci i czasy zbierania.

Usuwanie wystąpień równorzędnych

GC ma niekompletny widok procesu i może nie działać, gdy pamięć jest niska, ponieważ GC nie wie, że pamięć jest niska.

Na przykład wystąpienie typu Java.Lang.Object lub typu pochodnego ma rozmiar co najmniej 20 bajtów (może ulec zmianie bez powiadomienia itp.). Zarządzane otoki wywołujące nie dodają dodatkowych elementów członkowskich wystąpienia, więc jeśli masz wystąpienie Android.Graphics.Bitmap , które odwołuje się do 10 MB obiektu blob pamięci, GC platformy Xamarin.Android nie będzie wiedział, że — GC zobaczy obiekt 20-bajtowy i nie będzie mógł ustalić, czy jest połączony z obiektami przydzielonymi do środowiska uruchomieniowego systemu Android, które utrzymują 10 MB pamięci.

Często konieczne jest pomoc GC. Niestety, GC. AddMemoryPressure() i GC. Funkcja RemoveMemoryPressure() nie jest obsługiwana, więc jeśli wiesz, że właśnie zwolnino duży graf obiektów przydzielony przez język Java, może być konieczne ręczne wywołanie funkcji GC. Collect() w celu wyświetlenia monitu GC o wydanie pamięci po stronie języka Java lub jawnie usunąć podklasy Java.Lang.Object, przerywając mapowanie między zarządzaną otoką wywoływaną i wystąpieniem języka Java.

Uwaga

Podczas dyspozycji Java.Lang.Object wystąpień podklasy należy zachować szczególną ostrożność.

Aby zminimalizować możliwość uszkodzenia pamięci, należy przestrzegać poniższych wskazówek podczas wywoływania metody Dispose().

Udostępnianie między wieloma wątkami

Jeśli środowisko Java lub wystąpienie zarządzane może być współużytkowane między wieloma wątkami, nie powinno byćDispose() nigdy. Na przykład Typeface.Create()może zwrócić wystąpienie buforowane. Jeśli wiele wątków udostępnia te same argumenty, uzyska to samo wystąpienie. Dispose()W związku z tym ing Typeface wystąpienia z jednego wątku może unieważnić inne wątki, co może spowodować ArgumentExceptionwystąpienie z JNIEnv.CallVoidMethod() (między innymi), ponieważ wystąpienie zostało usunięte z innego wątku.

Usuwanie powiązanych typów języka Java

Jeśli wystąpienie jest powiązanym typem Języka Java, wystąpienie można usunąć tak długo, jak wystąpienie nie zostanie ponownie użyte z kodu zarządzanego, a wystąpienie języka Java nie może być współużytkowane przez wątki (zobacz poprzednią Typeface.Create() dyskusję). (Dokonanie tej determinacji może być trudne). Następnym razem, gdy wystąpienie języka Java wprowadzi kod zarządzany, zostanie utworzona nowa otoka.

Jest to często przydatne w przypadku elementów Drawables i innych wystąpień o dużym obciążeniu zasobami:

using (var d = Drawable.CreateFromPath ("path/to/filename"))
    imageView.SetImageDrawable (d);

Powyższe elementy są bezpieczne, ponieważ element równorzędny, który zwraca wartość Drawable.CreateFromPath(), będzie odwoływać się do elementu równorzędnego struktury, a nie elementu równorzędnego użytkownika. Wywołanie Dispose() na końcu using bloku spowoduje przerwanie relacji między zarządzanymi wystąpieniami drawable i platformą Drawable, dzięki czemu wystąpienie języka Java będzie zbierane natychmiast po zakończeniu działania systemu Android. Nie byłoby to bezpieczne, jeśli wystąpienie elementu równorzędnego odwołuje się do elementu równorzędnego użytkownika. W tym miejscu używamy informacji "zewnętrznych", aby wiedzieć, że Drawable element nie może odwoływać się do elementu równorzędnego użytkownika, a tym samym Dispose() połączenie jest bezpieczne.

Usuwanie innych typów

Jeśli wystąpienie odwołuje się do typu, który nie jest powiązaniem typu Java (na przykład niestandardowegoActivity), nie wywołaj Dispose() metody, chyba że wiesz, że żaden kod Java nie wywoła metod przesłonięć w tym wystąpieniu. Nie można tego zrobić, NotSupportedExceptionw s.

Jeśli na przykład masz niestandardowy odbiornik kliknięć:

partial class MyClickListener : Java.Lang.Object, View.IOnClickListener {
    // ...
}

Nie należy usuwać tego wystąpienia, ponieważ środowisko Java podejmie próbę wywołania metod w przyszłości:

// BAD CODE; DO NOT USE
Button b = FindViewById<Button> (Resource.Id.myButton);
using (var listener = new MyClickListener ())
    b.SetOnClickListener (listener);

Używanie jawnych kontroli w celu uniknięcia wyjątków

Jeśli zaimplementowano metodę przeciążenia Java.Lang.Object.Dispose , unikaj dotykania obiektów obejmujących interfejs JNI. Może to spowodować utworzenie sytuacji z podwójnym usuwaniem , która umożliwia kodowi (śmiertelnie) próbę uzyskania dostępu do bazowego obiektu Java, który został już zebrany przez śmieci. Spowoduje to wygenerowanie wyjątku podobnego do następującego:

System.ArgumentException: 'jobject' must not be IntPtr.Zero.
Parameter name: jobject
at Android.Runtime.JNIEnv.CallVoidMethod

Taka sytuacja występuje często, gdy pierwszy usunięcie obiektu powoduje, że element członkowski stanie się zerowy, a następnie kolejna próba dostępu na tym elemencie o wartości null powoduje zgłoszenie wyjątku. W szczególności obiekt Handle (który łączy wystąpienie zarządzane z jego bazowym wystąpieniem Java) jest unieważniany przy pierwszym usuwaniu, ale kod zarządzany nadal próbuje uzyskać dostęp do tego bazowego wystąpienia Java, mimo że nie jest już dostępne (zobacz Zarządzane zawinięcia z możliwością wywołania, aby uzyskać więcej informacji na temat mapowania między wystąpieniami Java i wystąpieniami zarządzanymi).

Dobrym sposobem zapobiegania temu wyjątkowi jest jawne sprawdzenie w Dispose metodzie, czy mapowanie między wystąpieniem zarządzanym a bazowym wystąpieniem języka Java jest nadal prawidłowe. Oznacza to, że sprawdź, czy obiekt Handle ma wartość null (IntPtr.Zero) przed uzyskaniem dostępu do jego elementów członkowskich. Na przykład następująca Dispose metoda uzyskuje childViews dostęp do obiektu:

class MyClass : Java.Lang.Object, ISomeInterface 
{
    protected override void Dispose (bool disposing)
    {
        base.Dispose (disposing);
        for (int i = 0; i < this.childViews.Count; ++i)
        {
            // ...
        }
    }
}

Jeśli początkowa passa usuwania powoduje childViews wystąpienie nieprawidłowego Handledostępu do pętli , for zostanie zgłoszony błąd ArgumentException. Dodając jawne Handle sprawdzanie wartości null przed pierwszym childViews dostępem, następująca Dispose metoda uniemożliwia wystąpienie wyjątku:

class MyClass : Java.Lang.Object, ISomeInterface 
{
    protected override void Dispose (bool disposing)
    {
        base.Dispose (disposing);

        // Check for a null handle:
        if (this.childViews.Handle == IntPtr.Zero)
            return;

        for (int i = 0; i < this.childViews.Count; ++i)
        {
            // ...
        }
    }
}

Zmniejsz liczbę wystąpień, do których odwołuje się odwołanie

Za każdym razem, gdy wystąpienie Java.Lang.Object typu lub podklasy jest skanowane podczas GC, cały graf obiektu, do którego odnosi się wystąpienie, musi być również skanowany. Graf obiektu jest zestawem wystąpień obiektów, do których odwołuje się "wystąpienie główne", oraz wszystkie elementy, do których odwołuje się wystąpienie główne, rekursywnie.

Rozważmy następującą klasę:

class BadActivity : Activity {

    private List<string> strings;

    protected override void OnCreate (Bundle bundle)
    {
        base.OnCreate (bundle);

        strings.Value = new List<string> (
                Enumerable.Range (0, 10000)
                .Select(v => new string ('x', v % 1000)));
    }
}

Po BadActivity utworzeniu wykres obiektu będzie zawierać 10004 wystąpień (1xBadActivity, 1x , 1x stringsstring[] przechowywanych przez stringswystąpienia ciągu 10000x), z których wszystkie będą musiały być skanowane przy każdym BadActivity skanowaniu wystąpienia.

Może to mieć szkodliwy wpływ na czasy kolekcji, co powoduje zwiększenie czasu wstrzymania GC.

Możesz pomóc GC, zmniejszając rozmiar grafów obiektów, które są zakorzenione przez wystąpienia równorzędne użytkownika. W powyższym przykładzie można to zrobić, przechodząc BadActivity.strings do oddzielnej klasy, która nie dziedziczy z obiektu Java.Lang.Object:

class HiddenReference<T> {

    static Dictionary<int, T> table = new Dictionary<int, T> ();
    static int idgen = 0;

    int id;

    public HiddenReference ()
    {
        lock (table) {
            id = idgen ++;
        }
    }

    ~HiddenReference ()
    {
        lock (table) {
            table.Remove (id);
        }
    }

    public T Value {
        get { lock (table) { return table [id]; } }
        set { lock (table) { table [id] = value; } }
    }
}

class BetterActivity : Activity {

    HiddenReference<List<string>> strings = new HiddenReference<List<string>>();

    protected override void OnCreate (Bundle bundle)
    {
        base.OnCreate (bundle);

        strings.Value = new List<string> (
                Enumerable.Range (0, 10000)
                .Select(v => new string ('x', v % 1000)));
    }
}

Kolekcje pomocnicze

Kolekcje pomocnicze mogą być wykonywane ręcznie przez wywołanie usługi GC. Collect(0). Kolekcje pomocnicze są tanie (w porównaniu z głównymi kolekcjami), ale mają znaczny koszt stały, więc nie chcesz ich zbyt często wyzwalać i powinny mieć czas wstrzymania o kilka milisekund.

Jeśli aplikacja ma "cykl dyżurowy", w którym odbywa się to samo, może być zalecane ręczne wykonanie drobnej kolekcji po zakończeniu cyklu dyżurnego. Przykładowe cykle pracy obejmują:

  • Cykl renderowania pojedynczej ramki gry.
  • Cała interakcja z danym oknem dialogowym aplikacji (otwieranie, wypełnianie, zamykanie)
  • Grupa żądań sieciowych do odświeżania/synchronizowania danych aplikacji.

Kolekcje główne

Główne kolekcje mogą być wykonywane ręcznie przez wywołanie usługi GC. Collect() lub GC.Collect(GC.MaxGeneration).

Powinny być wykonywane rzadko i mogą mieć czas wstrzymania sekundy na urządzeniu w stylu systemu Android podczas zbierania sterty 512 MB.

Główne kolekcje powinny być wywoływane ręcznie, jeśli kiedykolwiek:

Diagnostyka

Aby śledzić, kiedy odwołania globalne są tworzone i niszczone, można ustawić właściwość systemową debug.mono.log na wartość gref i/lub gc.

Konfigurowanie

Moduł zbierający śmieci platformy Xamarin.Android można skonfigurować, ustawiając zmienną MONO_GC_PARAMS środowiskową. Zmienne środowiskowe mogą być ustawiane za pomocą akcji Kompilacja systemu AndroidŚrodowisko.

Zmienna MONO_GC_PARAMS środowiskowa to rozdzielona przecinkami lista następujących parametrów:

  • nursery-size = size : ustawia rozmiar przedszkola. Rozmiar jest określony w bajtach i musi być mocą dwóch. Sufiksy k i mg mogą służyć do określania odpowiednio kilo-, mega-i gigabajtów. Przedszkole jest pierwszą generacją (z dwóch). Większe przedszkole zwykle przyspieszy program, ale będzie oczywiście używać więcej pamięci. Domyślny rozmiar przedszkola 512 kb.

  • soft-heap-limit = size : docelowe maksymalne użycie pamięci zarządzanej dla aplikacji. Gdy użycie pamięci jest poniżej określonej wartości, GC jest zoptymalizowany pod kątem czasu wykonywania (mniej kolekcji). Powyżej tego limitu GC jest zoptymalizowany pod kątem użycia pamięci (więcej kolekcji).

  • evacuation-threshold = próg : ustawia próg ewakuacji w procentach. Wartość musi być liczbą całkowitą z zakresu od 0 do 100. Wartość domyślna to 66. Jeśli faza zamiatania kolekcji wykryje, że zajętość określonego typu bloku sterty jest mniejsza niż ta wartość procentowa, zrobi to kopiowanie kolekcji dla tego typu bloku w następnej głównej kolekcji, co spowoduje przywrócenie zajętości do blisko 100 procent. Wartość 0 wyłącza ewakuację.

  • bridge-implementation = implementacja mostka: spowoduje to ustawienie opcji mostka GC w celu rozwiązania problemów z wydajnością GC. Istnieją trzy możliwe wartości: stary , nowy , tarjan.

  • bridge-require-precise-merge: Most Tarjan zawiera optymalizację, która może, w rzadkich przypadkach, spowodować, że obiekt zostanie zebrany jeden GC po raz pierwszy staje się bezużyteczny. Włączenie tej opcji powoduje wyłączenie tej optymalizacji, dzięki czemu GCs będą bardziej przewidywalne, ale potencjalnie wolniejsze.

Aby na przykład skonfigurować GC limit rozmiaru sterty o rozmiarze 128 MB, dodaj nowy plik do projektu za pomocą akcjiAndroidEnvironment Kompilacja z zawartością:

MONO_GC_PARAMS=soft-heap-limit=128m