Aracılığıyla paylaş


Atık Toplama

Xamarin.Android, Mono'nun Basit Nesil çöp toplayıcısını kullanır. Bu, iki nesil ve iki tür koleksiyon içeren büyük bir nesne alanı olan bir işaretleme ve süpürme çöp toplayıcıdır:

  • Küçük koleksiyonlar (0. Nesil yığınını toplar)
  • Ana koleksiyonlar (1. Nesil ve büyük nesne alanı yığınlarını toplar).

Not

GC aracılığıyla açık bir koleksiyonun olmaması halinde. Collect() koleksiyonları , yığın ayırmalarına bağlı olarak isteğe bağlıdır. Bu bir başvuru sayma sistemi değildir; bekleyen başvurular olmadığı veya bir kapsamdan çıkıldığında nesneler toplanmaz. Gc, ikincil yığında yeni ayırmalar için bellek yetersiz olduğunda çalışır. Ayırma yoksa çalışmaz.

Küçük koleksiyonlar ucuz ve sıktır ve yakın zamanda ayrılan ve kullanılmayan nesneleri toplamak için kullanılır. Küçük koleksiyonlar, ayrılan her birkaç MB nesneden sonra gerçekleştirilir. İkincil koleksiyonlar GC çağrılarak el ile gerçekleştirilebilir. Topla (0)

Büyük koleksiyonlar pahalı ve daha az sıklıktadır ve tüm ölü nesneleri geri kazanmak için kullanılır. Geçerli yığın boyutu için bellek tükendiğinde (yığını yeniden boyutlandırmadan önce) ana koleksiyonlar gerçekleştirilir. Ana koleksiyonlar GC çağrılarak el ile gerçekleştirilebilir. Toplama () veya GC çağırarak. GC bağımsız değişkeniyle (int) toplayın. MaxGeneration.

VM'ler Arası Nesne Koleksiyonları

Nesne türlerinin üç kategorisi vardır.

  • Yönetilen nesneler: Java.Lang.Object'ten devralmayantürler, örneğin System.String. Bunlar gc tarafından normal olarak toplanır.

  • Java nesneleri: Android çalışma zamanı VM'sinde bulunan ancak Mono VM'ye sunulmayan Java türleri. Bunlar sıkıcı ve daha fazla tartışılmayacaktır. Bunlar normalde Android çalışma zamanı VM'sinde toplanır.

  • Eş nesneleri: Tüm Java.Lang.Object ve Java.Lang.Throwable alt sınıfları gibi IJavaObject uygulayan türler. Bu türlerin örneklerinin yönetilen eş ve yerel olmak üzere iki "yarısı" vardır. Yönetilen eş, C# sınıfının bir örneğidir. Yerel eş, Android çalışma zamanı VM'sinde bir Java sınıfının örneğidir ve C# IJavaObject.Handle özelliği yerel eşe yönelik bir JNI genel başvurusu içerir.

İki tür yerel eş vardır:

Xamarin.Android işlemi içinde iki VM olduğundan, iki tür çöp toplama vardır:

  • Android çalışma zamanı koleksiyonları
  • Mono koleksiyonlar

Android çalışma zamanı koleksiyonları normal çalışır, ancak bir uyarıyla: JNI genel başvurusu GC kökü olarak değerlendirilir. Sonuç olarak, Bir Android çalışma zamanı VM nesnesi üzerinde tutan bir JNI genel başvurusu varsa, nesne koleksiyon için uygun olmasa bile toplanamaz .

Mono koleksiyonlar eğlencenin gerçekleştiği yerdir. Yönetilen nesneler normal şekilde toplanır. Eş nesneler aşağıdaki işlem gerçekleştirilerek toplanır:

  1. Mono koleksiyonu için uygun tüm Eş nesneleri JNI genel başvurusu JNI zayıf genel başvurusu ile değiştirildi.

  2. Android çalışma zamanı VM GC'si çağrılır. Herhangi bir Yerel eş örnek toplanabilir.

  3. (1) içinde oluşturulan JNI zayıf genel başvuruları denetlendi. Zayıf başvuru toplandıysa Eş nesnesi toplanır. Zayıf başvuru toplanmadıysa, zayıf başvuru bir JNI genel başvurusu ile değiştirilir ve Eş nesnesi toplanmaz. Not: API 14+ üzerinde bu, döndürülen değerin GC'den IJavaObject.Handle sonra değişebileceği anlamına gelir.

Tüm bunların sonucu, bir Peer nesnesinin bir örneğinin yönetilen kod (örneğin bir static değişkende depolanan) veya Java kodu tarafından başvurulacağı sürece yaşayacağıdır. Ayrıca, Yerel eşlerin yaşam süresi, hem Yerel eş hem de Yönetilen eş toplanabilir olana kadar Yerel eş toplanabilir olmayacağından, aksi takdirde yaşayacakları sürenin ötesine uzatılır.

Nesne Döngüleri

Eş nesneler hem Android çalışma zamanında hem de Mono VM'lerde mantıksal olarak bulunur. Örneğin, Android.App.Activity yönetilen eş örneğine karşılık gelen bir android.app.Activity framework eş Java örneği olacaktır. Java.Lang.Object dosyasından devralan tüm nesnelerin her iki VM'de de gösterimleri olması beklenebilir.

Her iki VM'de de temsili olan tüm nesnelerin ömrü, yalnızca tek bir VM içinde (örneğin) System.Collections.Generic.List<int>bulunan nesnelerle karşılaştırıldığında uzatılır. GC çağrılır . Xamarin.Android GC'nin toplamadan önce nesneye herhangi bir VM tarafından başvurulmadığından emin olması gerektiğinden Toplama işlemi bu nesneleri toplamaz.

Nesne ömrünü kısaltmak için Java.Lang.Object.Dispose() çağrılmalıdır. Bu işlem, genel başvuruyu serbest kaldırarak iki VM arasındaki nesne üzerindeki bağlantıyı el ile "kesecek" ve böylece nesnelerin daha hızlı toplanmasına olanak tanır.

Otomatik Koleksiyonlar

Sürüm 4.1.0'da başlayan Xamarin.Android, gref eşiği aşıldığında otomatik olarak tam GC gerçekleştirir. Bu eşik, platform için bilinen maksimum greflerin %90'ını oluşturur: öykünücüde 1800 gref (maksimum 2000) ve donanımda 46800 gref (en fazla 52000). Not: Xamarin.Android yalnızca Android.Runtime.JNIEnv tarafından oluşturulan grefs'leri sayar ve işlemde oluşturulan diğer grefleri bilmez. Bu sadece buluşsal bir şey.

Otomatik koleksiyon gerçekleştirildiğinde, hata ayıklama günlüğüne aşağıdakine benzer bir ileti yazdırılır:

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

Bunun oluşumu belirleyici değildir ve uygun olmayan zamanlarda (örneğin, grafik işlemenin ortasında) gerçekleşebilir. Bu iletiyi görürseniz, başka bir yerde açık bir koleksiyon gerçekleştirmek veya eş nesnelerin ömrünü azaltmayı denemek isteyebilirsiniz.

GC Köprüsü Seçenekleri

Xamarin.Android, Android ve Android çalışma zamanı ile saydam bellek yönetimi sunar. GC Köprüsü olarak adlandırılan Mono çöp toplayıcısının uzantısı olarak uygulanır.

GC Köprüsü, Mono çöp toplama sırasında çalışır ve Android çalışma zamanı yığınıyla hangi eş nesnelerin "canlılıklarına" ihtiyaç duyduğunu bulur. GC Köprüsü aşağıdaki adımları (sırayla) yaparak bu belirlemeyi yapar:

  1. Ulaşılamayan eş nesnelerin mono başvuru grafiğini temsil ettikleri Java nesnelerine dönüştürebilirsiniz.

  2. Java GC gerçekleştirme.

  3. Hangi nesnelerin gerçekten ölü olduğunu doğrulayın.

Bu karmaşık işlem, alt sınıflarının Java.Lang.Object herhangi bir nesneye serbestçe başvurmasını sağlayan işlemdir; Java nesnelerinin C# ile ilişkili olabileceği kısıtlamaları kaldırır. Bu karmaşıklık nedeniyle, köprü işlemi çok pahalı olabilir ve uygulamada fark edilebilir duraklamalara neden olabilir. Uygulamada önemli duraklamalar yaşanıyorsa aşağıdaki üç GC Köprüsü uygulamasından birini incelemeye değer:

  • Tarjan - Robert Tarjan'ın algoritması ve geriye dönük başvuru yayma temelinde GC Köprüsü'nün tamamen yeni bir tasarımı. Sanal iş yüklerimiz altında en iyi performansa sahip olmakla birlikte, deneysel kodun daha büyük bir payına da sahiptir.

  • Yeni - İkincil davranışın iki örneğini düzelten ancak çekirdek algoritmayı (Güçlü bir şekilde bağlı bileşenleri bulmak için Kosaraju algoritmasına göre) tutan özgün kodun önemli bir şekilde elden geçirilmesi.

  • Eski - Özgün uygulama (üçünün en kararlısı olarak kabul edilir). Bu, duraklatmalar kabul edilebilirse GC_BRIDGE uygulamanın kullanması gereken köprüdür.

Hangi GC Köprüsü'nün en iyi şekilde çalıştığını anlamanın tek yolu, bir uygulamada denemeler yapmak ve çıkışı analiz etmektir. Karşılaştırma için verileri toplamanın iki yolu vardır:

  • Günlüğü etkinleştirme - Her GC Köprüsü seçeneği için günlüğe kaydetmeyi etkinleştirin (Yapılandırma bölümünde açıklandığı gibi), ardından her ayardan günlük çıkışlarını yakalayıp karşılaştırın. GC Her seçenek için iletileri, özellikle GC_BRIDGE de iletileri inceleyin. Etkileşimli olmayan uygulamalar için 150 metreye kadar duraklamalar tolere edilebilir, ancak çok etkileşimli uygulamalar (oyunlar gibi) için 60ms'nin üzerinde duraklamalar bir sorundur.

  • Köprü muhasebesini etkinleştirme - Köprü muhasebesi, köprü işleminde yer alan her nesnenin işaret ettiği nesnelerin ortalama maliyetini görüntüler. Bu bilgilerin boyuta göre sıralanması, en fazla sayıda ek nesnenin ne olduğuna ilişkin ipuçları sağlar.

Varsayılan ayar Tarjan'dır. Bir regresyon bulursanız, bu seçeneği Eski olarak ayarlamanız gerekebilir. Ayrıca, Tarjan performansta bir iyileştirme üretmezse daha kararlı Eski seçeneğini kullanmayı seçebilirsiniz.

Bir uygulamanın hangi GC_BRIDGE seçeneği kullanması gerektiğini belirtmek için, öğesini veya bridge-implementation=tarjan ortam değişkenine MONO_GC_PARAMS geçirinbridge-implementation=oldbridge-implementation=new. Bu, projenize build eylemiyleAndroidEnvironmentyeni bir dosya ekleyerek gerçekleştirilir. Örneğin:

MONO_GC_PARAMS=bridge-implementation=tarjan

Daha fazla bilgi için bkz. Yapılandırma.

GC'ye Yardımcı Olma

GC'nin bellek kullanımını ve toplama sürelerini azaltmasına yardımcı olmanın birden çok yolu vardır.

Eş örneklerin atılması

GC, işlemin tamamlanmamış bir görünümüne sahiptir ve gc belleğin düşük olduğunu bilmediğinden bellek düşük olduğunda çalışmayabilir.

Örneğin, Java.Lang.Object türünün veya türetilmiş türün bir örneği en az 20 bayt boyutundadır (bildirimde bulunmadan değiştirilebilir vb.). Yönetilen Çağrılabilen Sarmalayıcılar ek örnek üyeleri eklemez, bu nedenle 10 MB bellek blobunu ifade eden bir Android.Graphics.Bitmap örneğinize sahip olduğunuzda, Xamarin.Android'in GC'sinin bunu bilmediğinden GC 20 baytlık bir nesne görür ve 10 MB belleği canlı tutan Android çalışma zamanı tarafından ayrılmış nesnelere bağlandığını belirleyemez.

GC'ye yardımcı olmak sık sık gereklidir. Ne yazık ki GC. AddMemoryPressure() ve GC. RemoveMemoryPressure() desteklenmez, bu nedenle java tarafından ayrılmış büyük bir nesne grafı serbest bırakıldığını biliyorsanız GC'yi el ile çağırmanız gerekebilir. Bir GC'den Java tarafı belleği serbest bırakmasını isteyen Collect() veya Java.Lang.Object alt sınıflarını açıkça atarak yönetilen çağrılabilen sarmalayıcı ile Java örneği arasındaki eşlemeyi bozabilirsiniz.

Not

Alt sınıf örneklerini atarken Java.Lang.Object son derece dikkatli olmanız gerekir.

Bellek bozulması olasılığını en aza indirmek için çağrısı Dispose()yaparken aşağıdaki yönergelere bakın.

Birden Çok İş Parçacığı Arasında Paylaşım

Java veya yönetilen örnek birden çok iş parçacığı arasında paylaşılabilirse, hiçbir zaman d olmamalıdırDispose(). Örneğin Typeface.Create() önbelleğe alınmış bir örnek döndürebilir. Birden çok iş parçacığı aynı bağımsız değişkenleri sağlarsa, aynı örneği alır. Sonuç olarak, Dispose()örneğin bir iş parçacığından lanması Typeface diğer iş parçacıklarını geçersiz kılabilir ve bu da örneğin başka bir iş parçacığından JNIEnv.CallVoidMethod() atılması nedeniyle diğer iş parçacıklarından (diğerlerinin yanı) s'ye neden ArgumentExceptionolabilir.

Bağlı Java Türlerini Yok Etme

Örnek bağlı bir Java türündeyse, örnek yönetilen koddan yeniden kullanılamayacağı ve Java örneğinin iş parçacıkları arasında paylaşılmayacağı sürece örnek atılabilir (önceki Typeface.Create() tartışmaya bakın). (Bu belirlemeyi yapmak zor olabilir.) Java örneği yönetilen koda bir sonraki girişinde, bunun için yeni bir sarmalayıcı oluşturulur.

Bu, Drawables ve diğer yoğun kaynak örnekleri söz konusu olduğunda sık sık yararlıdır:

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

Yukarıdaki güvenlidir çünkü Drawable.CreateFromPath() tarafından döndürülen Eş, Kullanıcı eşlerine değil Bir Çerçeve eşine başvurur. Dispose() Bloğun using sonundaki çağrı, yönetilen Drawable ve framework Drawable örnekleri arasındaki ilişkiyi keserek Java örneğinin Android çalışma zamanının gerek duyduğu anda toplanmasına olanak tanır. Eş örneği bir Kullanıcı eşine başvuruda bulunursa bu güvenli olmaz; burada kullanıcı eşlerine başvuramadığını ve bu nedenle çağrının DrawableDispose() güvenli olduğunu bilmek için "dış" bilgileri kullanıyoruz.

Diğer Türleri Yok Etme

Örnek, Java türünün bağlaması olmayan bir türe başvuruyorsa (özel gibi), hiçbir Java kodunun bu örnekte geçersiz kılınan yöntemleri çağırmayacağını bilmiyorsanızÇAĞıRMAYINDispose().Activity Bunun yapılmaması, s ile sonuçlanmamasına NotSupportedExceptionneden olur.

Örneğin, özel tıklama dinleyiciniz varsa:

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

Java gelecekte üzerinde yöntemler çağırmaya çalışacağından bu örneği atmamalısınız:

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

Özel Durumları Önlemek için Açık Denetimler Kullanma

Java.Lang.Object.Dispose aşırı yükleme yöntemi uyguladıysanız JNI içeren nesnelere dokunmaktan kaçının. Bunu yapmak, kodunuzun zaten çöp olarak toplanmış temel alınan bir Java nesnesine erişmeyi (önemli ölçüde) denemesini mümkün kılan bir çift atma durumu oluşturabilir. Bunun yapılması aşağıdakine benzer bir özel durum oluşturur:

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

Bu durum genellikle bir nesnenin ilk atılması bir üyenin null olmasına neden olduğunda ve sonra bu null üyede sonraki bir erişim girişimi bir özel durumun oluşmasına neden olduğunda oluşur. Özellikle, nesnenin Handle (yönetilen örneği temel alınan Java örneğine bağlayan) ilk atmada geçersiz kılınmıştır, ancak yönetilen kod artık kullanılamasa bile bu temel Java örneğine erişmeye çalışır (Java örnekleri ile yönetilen örnekler arasındaki eşleme hakkında daha fazla bilgi için bkz . Yönetilen Çağrılabilen Sarmalayıcılar ).

Bu özel durumu önlemenin iyi bir yolu, yönteminizde Dispose yönetilen örnek ile temel alınan Java örneği arasındaki eşlemenin hala geçerli olduğunu açıkça doğrulamaktır; diğer bir deyişle, üyelerine erişmeden önce nesnenin Handle null (IntPtr.Zero) olup olmadığını denetleyin. Örneğin, aşağıdaki Dispose yöntem bir childViews nesneye erişir:

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

İlk atma geçişi geçersiz forHandlebir geçişe neden olursachildViews, döngü erişimi bir ArgumentExceptionoluşturur. İlk childViews erişimden önce açık Handle bir null denetimi ekleyerek, aşağıdaki Dispose yöntem özel durumun oluşmasını engeller:

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)
        {
            // ...
        }
    }
}

Başvurulacak Örnekleri Azaltma

GC sırasında bir Java.Lang.Object tür veya alt sınıfın örneği her taransa, örneğin başvurduğu nesne grafiğinin tamamı da taranmalıdır. Nesne grafı, "kök örneğinin" başvurduğu nesne örnekleri kümesidir ve ayrıca kök örneğin başvurduğu her şey özyinelemeli olarak kullanılır.

Aşağıdaki sınıfı göz önünde bulundurun:

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)));
    }
}

BadActivity Nesne grafı oluşturulduğunda, 10004 örnek (1x , 1x stringsBadActivity, tarafından tutulan strings1x string[] , 10000x dize örnekleri) içerir ve bunların tümü her örnek tarandığında BadActivity taranmalıdır.

Bunun koleksiyon sürelerinizi olumsuz etkileyerek GC duraklatma sürelerinin artmasına neden olabilir.

Kullanıcı eş örnekleri tarafından köklenen nesne grafiklerinin boyutunu küçülterek GC'ye yardımcı olabilirsiniz. Yukarıdaki örnekte bu işlem Java.Lang.Object dosyasından devralınmayan ayrı bir sınıfa geçirilerek BadActivity.strings yapılabilir:

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)));
    }
}

Küçük Koleksiyonlar

İkincil koleksiyonlar GC çağrılarak el ile gerçekleştirilebilir. Topla(0). Küçük koleksiyonlar ucuz (büyük koleksiyonlarla karşılaştırıldığında) ancak önemli bir sabit maliyeti vardır, bu nedenle bunları çok sık tetiklemeniz gerekmez ve birkaç milisaniyelik bir duraklatma süresine sahip olmanız gerekir.

Uygulamanızın aynı şeyin tekrar tekrar yapıldığı bir "görev döngüsü" varsa, görev döngüsü sona erdikten sonra küçük bir koleksiyonu el ile gerçekleştirmeniz önerilir. Örnek görev döngüleri şunlardır:

  • Tek bir oyun çerçevesinin işleme döngüsü.
  • Belirli bir uygulama iletişim kutusuyla tüm etkileşim (açma, doldurma, kapatma)
  • Uygulama verilerini yenilemek/eşitlemek için bir grup ağ isteği.

Ana Koleksiyonlar

Ana koleksiyonlar GC çağrılarak el ile gerçekleştirilebilir. Collect() veya GC.Collect(GC.MaxGeneration).

Bunlar nadiren gerçekleştirilmelidir ve 512 MB yığını toplarken Android stili bir cihazda saniyelik bir duraklatma süresine sahip olabilir.

Ana koleksiyonlar yalnızca el ile çağrılmalıdır( varsa):

Tanılama

Genel başvuruların ne zaman oluşturulup yok edildiğini izlemek için, debug.mono.log sistem özelliğini gref ve/veya gc içerecek şekilde ayarlayabilirsiniz.

Yapılandırma

Xamarin.Android çöp toplayıcısı ortam değişkeni ayarlanarak MONO_GC_PARAMS yapılandırılabilir. Ortam değişkenleri, AndroidEnvironment Derleme eylemiyle ayarlanabilir.

Ortam MONO_GC_PARAMS değişkeni, aşağıdaki parametrelerin virgülle ayrılmış bir listesidir:

  • nursery-size = boyut : Çocuk odasının boyutunu ayarlar. Boyut bayt cinsinden belirtilir ve iki üssü olmalıdır. ve sonekleri kmg sırasıyla kilo, mega ve gigabaytları belirtmek için kullanılabilir. Kreş birinci nesildir (iki nesil). Daha büyük bir kreş genellikle programı hızlandırır ancak açıkçası daha fazla bellek kullanacaktır. Varsayılan kreş boyutu 512 kb.

  • soft-heap-limit = boyut : Uygulama için hedef maksimum yönetilen bellek tüketimi. Bellek kullanımı belirtilen değerin altında olduğunda, GC yürütme süresi (daha az koleksiyon) için iyileştirilir. Bu sınırın üzerinde GC, bellek kullanımı (daha fazla koleksiyon) için iyileştirilmiştir.

  • evacuation-threshold = eşik : Tahliye eşiğini yüzde olarak ayarlar. Değer, 0 ile 100 arasında bir tamsayı olmalıdır. Varsayılan değer 66'dır. Koleksiyonun süpürme aşaması belirli bir yığın bloğu türünün doluluk oranının bu yüzdeden az olduğunu bulursa, sonraki ana koleksiyonda bu blok türü için bir kopyalama koleksiyonu yapar ve böylece doluluk yüzde 100'e yakın olacak şekilde geri yüklenir. 0 değeri tahliyeyi kapatır.

  • bridge-implementation = köprü uygulaması : Bu, GC performans sorunlarını gidermeye yardımcı olmak için GC Köprüsü seçeneğini ayarlar. Üç olası değer vardır: eski , yeni , tarjan.

  • bridge-require-precise-merge: Tarjan köprüsü, nadir durumlarda bir nesnenin ilk çöp haline geldikten sonra bir GC toplanmasına neden olabilecek bir iyileştirme içerir. Bu seçenek dahil olmak bu iyileştirmeyi devre dışı bırakır ve GC'leri daha öngörülebilir ancak potansiyel olarak daha yavaş hale getirir.

Örneğin, GC'yi 128 MB yığın boyutu sınırına sahip olacak şekilde yapılandırmak için, içeriğiyle birlikte Derleme eylemiyleAndroidEnvironment Projenize yeni bir dosya ekleyin:

MONO_GC_PARAMS=soft-heap-limit=128m