.NET'te çöp toplamayı anlama


170

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

public class Class1
{
    public static int c;
    ~Class1()
    {
        c++;
    }
}

public class Class2
{
    public static void Main()
    {
        {
            var c1=new Class1();
            //c1=null; // If this line is not commented out, at the Console.WriteLine call, it prints 1.
        }
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine(Class1.c); // prints 0
        Console.Read();
    }
}

Şimdi, ana yöntemdeki c1 değişkeni kapsam dışı olsa ve GC.Collect()çağrıldığında başka bir nesne tarafından daha fazla referans edilmese de , neden orada sonlandırılmıyor?


8
GC kapsam dışı olduklarında örnekleri hemen serbest bırakmaz. Gerekli olduğunu düşündüğünde bunu yapar. GC hakkında her şeyi buradan okuyabilirsiniz: msdn.microsoft.com/en-US/library/vstudio/0xy59wtx.aspx
user1908061 16:13

@ user1908061 (Pssst. Bağlantınız koptu.)
Dragomok

Yanıtlar:


352

Burada devreye giriyorsunuz ve çok yanlış sonuçlar çıkarıyorsunuz çünkü bir hata ayıklayıcı kullanıyorsunuz. Kodunuzu, kullanıcının makinesinde çalıştığı şekilde çalıştırmanız gerekir. Build + Configuration manager ile önce Release derlemesine geçin, sol üst köşedeki "Active solution configuration" kombinasyonunu "Release" olarak değiştirin. Ardından, Araçlar + Seçenekler, Hata Ayıklama, Genel'e gidin ve "JIT optimizasyonunu bastır" seçeneğinin işaretini kaldırın.

Şimdi programınızı tekrar çalıştırın ve kaynak kodunu kullanın. Ekstra parantezlerin nasıl hiçbir etkisi olmadığını unutmayın. Ve değişkenin null değerine ayarlanmasının hiç fark yaratmadığını unutmayın. Her zaman "1" yazdıracaktır. Artık umduğunuz şekilde çalışıyor ve çalışmasını bekliyorsunuz.

Bu hata ayıklama derlemesini çalıştırdığınızda neden bu kadar farklı çalıştığını açıklamak görev ile ayrılır. Bu, çöp toplayıcının yerel değişkenleri nasıl keşfettiğini ve bunun bir hata ayıklayıcının mevcut olmasından nasıl etkilendiğini açıklamayı gerektirir.

Öncelikle, jitter IL'yi bir yöntem için makine koduna derlediğinde iki önemli görevi yerine getirir . İlki hata ayıklayıcıda çok görünür, hata ayıklama + Windows + Sökme penceresiyle makine kodunu görebilirsiniz. Ancak ikinci görev tamamen görünmezdir. Ayrıca, yöntem gövdesindeki yerel değişkenlerin nasıl kullanıldığını açıklayan bir tablo oluşturur. Bu tabloda, her yöntem bağımsız değişkeni ve iki adresli yerel değişken için bir giriş vardır. Değişkenin önce bir nesne başvurusunu depolayacağı adres. Ve bu değişkenin artık kullanılmadığı makine kodu komutunun adresi. Ayrıca bu değişkenin yığın karesinde veya işlemci kaydında depolanıp depolanmadığı.

Bu tablo çöp toplayıcı için gereklidir, bir koleksiyon gerçekleştirirken nesne referanslarını nerede arayacağını bilmelidir. Referans GC yığınındaki bir nesnenin parçası olduğunda bunu yapmak oldukça kolaydır. Nesne referansı bir CPU kaydında saklandığında kesinlikle kolay değildir. Tablo nereye bakacağınızı söylüyor.

Tablodaki "artık kullanılmıyor" adresi çok önemlidir. Çöp toplayıcıyı çok verimli hale getirir . Bir yöntemin içinde kullanılsa ve bu yöntemin henüz yürütülmesi tamamlanmamış olsa bile, bir nesne başvurusu toplayabilir. Çok yaygın olan, örneğin Main () yönteminiz yalnızca programınız sona ermeden hemen önce yürütmeyi durdurur. Açıkça, bu Main () yöntemi içinde kullanılan nesne başvurularının program süresince yaşamasını istemezsiniz, bu da bir sızıntıya neden olur. Jitter, programın çağrı yapılmadan önce bu Main () yöntemi içinde ne kadar ilerlediğine bağlı olarak böyle bir yerel değişkenin artık yararlı olmadığını keşfetmek için tabloyu kullanabilir.

Bu tabloyla ilgili neredeyse sihirli bir yöntem GC.KeepAlive (). Bu ise çok hepsi de herhangi bir kod oluşturmaz, özel bir yöntem. Tek görevi bu tabloyu değiştirmek. Bu uzanıryerel değişkenin ömrü, depoladığı referansın çöp toplanmasını önler. Kullanmanız gereken tek zaman, GC'nin bir referans toplama konusunda aşırı istekli olmasını durdurmaktır; bu, yönetilmeyen koda bir referansın geçtiği birlikte çalışma senaryolarında olabilir. Çöp toplayıcı, jitter tarafından derlenmediği için bu tür kodlar tarafından kullanılan bu referansları göremez, bu nedenle referansı nerede arayacağını söyleyen tabloya sahip değildir. Temsilci nesnesini EnumWindows () gibi yönetilmeyen bir işleve iletmek, GC.KeepAlive () kullanmanız gereken zamanın ortak örneğidir.

Yani, sürüm oluşturma bunu çalıştırdıktan sonra numune pasajımızdaki söyleyebilirim, yerel değişkenler olabilir yöntem çalışmayı tamamlandığında önce erken toplanan olsun. Bu yöntem artık işaret ederse onun yöntemlerinden biri çalışırken bile daha güçlü, bir nesne toplanan alabilirsiniz bu . Bununla ilgili bir sorun var, böyle bir yöntemi ayıklamak çok garip. Değişkeni İzleme penceresine iyi yerleştirebileceğiniz veya inceleyebileceğiniz için. Ve bu olur kaybolur bir GC oluşursa hata ayıklaması yaptığınız. Bu çok tatsız olurdu, bu yüzden jitter bağlı bir hata ayıklayıcı olduğunun farkında . Daha sonra değiştirirtablo ve "son kullanılan" adresi değiştirir. Ve normal değerinden yöntemdeki son komutun adresine değiştirir. Bu, yöntem dönmediği sürece değişkeni canlı tutar. Hangi yöntem geri gelene kadar izlemeye devam etmenizi sağlar.

Bu şimdi daha önce gördüklerinizi ve soruyu neden sorduğunuzu da açıklıyor. GC.Collect çağrısı referans toplayamadığından "0" yazdırır. Tablo değişken kullanımda olduğunu söylüyor geçmiş tüm yol yukarı yönteminin sonuna, GC.Collect () çağrısı. Hata ayıklayıcıyı ekleyerek ve Hata Ayıklama derlemesini çalıştırarak bunu yapmaya zorladı.

Değişkeni null olarak ayarlamak artık bir etkiye sahip çünkü GC değişkeni inceleyecek ve artık bir referans görmeyecek. Ancak, birçok C # programcısının düştüğü tuzağa düşmediğinizden emin olun, aslında bu kodu yazmak anlamsızdı. Release derlemesinde kodu çalıştırdığınızda bu deyimin var olup olmadığı hiçbir fark yaratmaz. Aslında, jitter optimizer herhangi bir etkisi olmadığından bu ifadeyi kaldıracaktır . Etkisi varmış gibi görünse de böyle bir kod yazmamaya dikkat edin .


Bu konu hakkında son bir not, bir Office uygulamasıyla bir şeyler yapmak için küçük programlar yazan programcıların başını belaya sokan şeydir. Hata ayıklayıcı genellikle Yanlış Yolda alır, Office programının istek üzerine çıkmasını ister. Bunu yapmanın uygun yolu GC.Collect () öğesini çağırmaktır. Ancak, uygulamalarında hata ayıkladıklarında işe yaramadığını keşfedecekler ve Marshal.ReleaseComObject () 'i çağırarak onları asla asla karaya götürmeyecekler. Manuel bellek yönetimi, nadiren düzgün bir şekilde çalışır, çünkü görünmez bir arayüz referansını kolayca görmezden gelirler. GC.Collect () aslında, uygulama hata ayıklama zaman değil çalışır.


1
Hans'ın benim için güzelce cevapladığı soruma da bakın. stackoverflow.com/questions/15561025/…
Dave Nay

1
@HansPassant Burada da sorumun bir kısmını yanıtlayan bu harika açıklamayı buldum: GC ve evre senkronizasyonu hakkında stackoverflow.com/questions/30529379/… . Hala sahip olduğum bir soru: GC'nin gerçekten bir kayıtta kullanılan (askıya alınırken bellekte depolanan) adresleri sıkıştırıp güncellemediğini veya güncellediğini mi merak ediyorsunuz? İş parçacığını askıya aldıktan sonra (devam etmeden önce) kayıtları güncelleyen bir işlem bana OS tarafından engellenen ciddi bir güvenlik iş parçacığı gibi geliyor.
atlaste

Dolaylı olarak, evet. İş parçacığı askıya alınır, GC, CPU kayıtları için destek deposunu günceller. İş parçacığı çalışmaya devam ettiğinde, artık güncelleştirilmiş kayıt değerlerini kullanır.
Hans Passant

1
@HansPassant, burada açıkladığınız CLR çöp toplayıcısının açık olmayan bazı ayrıntıları için referanslar eklerseniz sevinirim?
denfromufa

Yapılandırmanın akıllıca olduğu önemli bir nokta, "Kodu optimize et" ( <Optimize>true</Optimize>in .csproj) özelliğinin etkinleştirilmiş olmasıdır. Bu, "Serbest Bırak" yapılandırmasında varsayılan değerdir. Ancak özel yapılandırmalar kullanılması durumunda, bu ayarın önemli olduğunu bilmek önemlidir.
Sıfır3

34

[Sadece İçselleştirme Süreci sürecini daha fazla eklemek istedim]

Böylece, bir nesne oluşturursunuz ve nesne toplandığında, nesnenin Finalizeyöntemi çağrılmalıdır. Ancak sonuçlandırmada bu çok basit varsayımdan daha fazlası var.

KISA KAVRAMLAR ::

  1. Nesneleri uygulama Finalizeyöntemleri DEĞİL , orada Bellek hemen geri alınır, tabii ki
    uygulama koduyla artık erişilemezse

  2. Uygulanması Nesneleri FinalizeYöntem, Concept / Uygulanmasına Application Roots, Finalization Queue, Freacheable Queuebunlar geri alınabilir önce gelir.

  3. Uygulama Kodu ile erişilemezse, herhangi bir nesne çöp sayılır

Varsayalım :: Sınıflar / Nesneler A, B, D, G, H FinalizeMetodu uygulamıyor ve C, E, F, I, J Finalizemetodu uygulamıyor .

Bir uygulama yeni bir nesne oluşturduğunda, yeni operatör belleği yığıntan ayırır. Nesnenin türü bir Finalizeyöntem içeriyorsa , sonlandırma kuyruğuna nesneye bir işaretçi yerleştirilir .

bu nedenle C, E, F, I, J nesnelerine işaretçiler, sonlandırma sırasına eklenir. Sonlandırma sıra çöp toplayıcı tarafından kontrol edilen bir iç veri yapısıdır. Kuyruktaki her girdi , nesnenin belleği geri alınmadan önce yönteminin çağrılması gereken bir nesneyi gösterir . Aşağıdaki şekilde birkaç nesne içeren bir yığın gösterilmektedir. Bu nesnelere uygulama köklerinden ulaşılabilir

Finalizeve bazıları değil. C, E, F, I ve J nesneleri oluşturulduğunda .Net çerçevesi, bu nesnelerin Finalizeyöntemlere sahip olduğunu algılar ve bu nesnelere sonlandırma kuyruğuna işaretçiler eklenir .

resim açıklamasını buraya girin

Bir GC oluştuğunda (1. Toplama), B, E, G, H, I ve J nesneleri çöp olarak belirlenir. Çünkü A, C, D, F hala yukarıdaki sarı kutudan oklarla gösterilen Uygulama Kodu ile erişilebilir.

Çöp toplayıcı , bu nesnelere işaretçi arayan sonlandırma sırasını tarar . Bir işaretçi bulunduğunda, işaretçi sonlandırma kuyruğundan kaldırılır ve freachable kuyruğuna eklenir ("F ulaşılabilir"). Freachable sıra çöp toplayıcı tarafından kontrol edilen başka bir dahili veri yapısıdır. Ulaşılabilir kuyruktaki her işaretçi , yönteminin çağrılması için hazır olan bir nesneyi tanımlar .

Finalize

Koleksiyondan sonra (1. Koleksiyon), yönetilen yığın aşağıdaki şekle benzer bir şeye benziyor. Aşağıda verilen açıklamalar ::
1.) B, G ve H nesneleri tarafından kullanılan bellek derhal geri kazanılmıştır, çünkü bu nesnelerin çağrılması gereken bir sonlandırma yöntemi yoktur .

2.) Bununla birlikte, E, I ve J nesneleri tarafından işgal edilen bellek, Finalizeyöntemleri henüz çağrılmadığından geri alınamaz. Sonlandırma yönteminin çağrılması, sıralanabilir kuyruk tarafından yapılır .

3.) A, C, D, F hala yukarıdaki sarı kutudan oklarla gösterilen Uygulama Kodu ile erişilebilir, bu yüzden her durumda toplanmayacaklardır

resim açıklamasını buraya girin

Sonlandırma yöntemlerini çağırmaya adanmış özel bir çalışma zamanı iş parçacığı vardır. Ulaşılabilir kuyruk boş olduğunda (genellikle durum budur), bu iş parçacığı uyur. Ancak girdiler göründüğünde, bu iş parçacığı uyanır, her girdiyi kuyruktan kaldırır ve her nesnenin Sonlandırma yöntemini çağırır. Çöp toplayıcı, geri kazanılabilir belleği sıkıştırır ve özel çalışma zamanı iş parçacığı , her nesnenin Finalizeyöntemini çalıştırarak freachable kuyruğunu boşaltır . Son olarak burada, Sonlandırma yönteminiz yürütüldüğünde

Bir dahaki sefere çöp toplayıcı çağrıldığında (2. Toplama), uygulamanın kökleri ona işaret etmediği ve freachable kuyruğu artık onu işaret etmediği için (nihai Koleksiyon da), kesinleşmiş nesnelerin gerçekten çöp olduğunu görür . nesneler için bellek (E, I, J) sadece Heap'ten geri kazanılır. Aşağıdaki şekle bakın ve yukarıdaki rakamla karşılaştırın

resim açıklamasını buraya girin

Burada anlaşılması gereken önemli nokta, sonlandırma gerektiren nesneler tarafından kullanılan belleği geri kazanmak için iki GC'nin gerekli olmasıdır . Gerçekte, bu nesneler daha eski bir nesle yükseltilebileceğinden ikiden fazla koleksiyon kabini bile gereklidir

NOT :: freachable kuyruk küresel ve statik değişkenler kökleri vardır tıpkı bir kök olarak kabul edilir. Bu nedenle, bir nesne freachable kuyruğundaysa, nesneye ulaşılabilir ve çöp değildir.

Son bir not olarak, hata ayıklama uygulamasının bir şey olduğunu unutmayın, Çöp Toplama başka bir şeydir ve farklı çalışır. Şimdiye kadar, çöp toplama işlemini sadece uygulamalarda hata ayıklama yoluyla HİSSEDEMİYORSUNUZ .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.