C # 'da Çöp Toplamaya Zorlamak İçin En İyi Uygulama


118

Tecrübelerime göre, çoğu insan size çöp toplamaya zorlamanın akıllıca olmadığını söyleyecektir, ancak 0 neslinde her zaman toplanmayan ancak belleğin bir sorun olduğu bazı durumlarda büyük nesnelerle çalışıyorsanız, tahsilatı zorlamak uygun mu? Bunu yapmak için bir en iyi uygulama var mı?

Yanıtlar:


112

En iyi uygulama, çöp toplamayı zorlamamaktır.

MSDN'ye göre:

"Collect'i çağırarak çöp toplamayı zorlamak mümkündür, ancak çoğu zaman bundan kaçınılmalıdır çünkü performans sorunları yaratabilir."

Ancak, Collect () çağrısının olumsuz bir etkisi olmayacağını doğrulamak için kodunuzu güvenilir bir şekilde test edebilirseniz, devam edin ...

Artık ihtiyacınız kalmadığında nesnelerin temizlendiğinden emin olmaya çalışın. Özel nesneleriniz varsa, "using deyimi" ve IDisposable arabirimini kullanmaya bakın.

Bu bağlantı, hafızayı boşaltma / çöp toplama vb. İle ilgili bazı iyi pratik tavsiyelere sahiptir:

http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx



4
Nesneleriniz yönetilmeyen belleğe işaret ediyorsa, çöp toplayıcının GC.AddMemoryPressure Api ( msdn.microsoft.com/en-us/library/… ) aracılığıyla bilmesini sağlayabilirsiniz . Bu, çöp toplayıcısına, toplama algoritmalarına müdahale etmeden sisteminiz hakkında daha fazla bilgi verir.
Govert

+1: * "using ifadesi" ve IDisposable arayüzü kullanmaya bakın. * Son çare dışında zorlamayı düşünmezdim - iyi tavsiye ('feragat' olarak okuyun). Bununla birlikte, arka uç işleminde aktif bir referansı kaybetmeyi simüle etmek için bir birim testinde toplamayı zorluyorum - sonuçta bir TargetOfInvocationNullException.
IAbstract

33

Şöyle bir bakın - çöp tenekesi% 10'dayken mutfak çöpünü atmak mı yoksa çıkarmadan önce doldurmasını sağlamak mı daha verimli?

Dolmasına izin vermeyerek, dışarıdaki çöp kutusuna gidip gelirken zamanınızı boşa harcarsınız. Bu, GC iş parçacığı çalıştığında olana benzer - tüm yönetilen iş parçacıkları çalışırken askıya alınır. Ve yanılmıyorsam, GC iş parçacığı birden fazla AppDomains arasında paylaşılabilir, bu nedenle çöp toplama hepsini etkiler.

Elbette yakın zamanda çöp tenekesine hiçbir şey eklemeyeceğiniz bir durumla karşılaşabilirsiniz - mesela, tatile çıkacaksanız. O halde dışarı çıkmadan önce çöpü atmak iyi bir fikir olacaktır.

Bu, bir GC'yi zorlamanın yardımcı olabileceği bir kez OLABİLİR - programınız boşta kalırsa, kullanımdaki bellek çöp olarak toplanmaz çünkü ayırma yoktur.


5
Eğer onu bir dakikadan fazla bırakırsanız ölecek bir bebeğiniz olsaydı ve çöpü halletmek için sadece bir dakikanız olsaydı, o zaman her seferinde hepsini yapmak yerine her seferinde biraz yapmak istersiniz. Ne yazık ki, GC :: Collect () yöntemi ne kadar sık ​​çağırırsanız o kadar hızlı değildir. Yani gerçek zamanlı bir motor için, sadece atma mekanizmasını kullanamazsanız ve GC'nin verilerinizi toplamasına izin veremezseniz, cevabıma göre (muhtemelen bunun altında, lol) yönetilen bir sistem kullanmamalısınız.
Jin

1
Benim durumumda, performans ayarlaması için TEKRARLA bir A * (en kısa yol) algoritması çalıştırıyorum ... bu, üretimde yalnızca bir kez çalıştırılacak (her "haritada"). Bu nedenle, GC'nin her bir yinelemeden önce, "performans ölçülen bloğum" dışında yapılmasını istiyorum, çünkü üretimdeki durumu daha yakından modellediğini hissediyorum, burada GC her "haritada" gezindikten sonra zorlanmalı / zorlanmalı.
corlettk

Ölçülen bloktan önce GC çağırmak aslında üretimdeki durumu modellemez çünkü üretimde GC öngörülemeyen zamanlarda yapılacaktır. Bunu azaltmak için, birkaç GC yürütmesini içerecek uzun bir ölçüm yapmalı ve GC sırasındaki zirveleri analiz ve istatistiklerinize dahil etmelisiniz.
Ran

32

En iyi uygulama, çoğu durumda çöp toplamayı zorlamamaktır. (Üzerinde çalıştığım her sistem çöp toplamaya zorladı, çözülürse çöp toplamayı zorlama ihtiyacını ortadan kaldıracak ve sistemi büyük ölçüde hızlandıracak sorunların altını çizen her sistem.)

Bir var birkaç durum size daha sonra çöp toplayıcı yapar bellek kullanımı hakkında daha fazla bilgi. Bu, çok kullanıcılı bir uygulamada veya bir seferde birden fazla isteğe yanıt veren bir hizmette olası değildir.

Bununla birlikte, bazı parti tipi işlemelerde , GC'den daha fazlasını bilirsiniz. Örneğin bir uygulama düşünün.

  • Komut satırında dosya adlarının bir listesi verilir
  • Tek bir dosyayı işler ve ardından sonucu bir sonuç dosyasına yazar.
  • Dosyayı işlerken, dosyanın işlenmesi tamamlanana kadar toplanamayacak çok sayıda birbirine bağlı nesne oluşturur (örneğin bir ayrıştırma ağacı)
  • İşlediği dosyalar arasında fazla bir durum tutmaz .

Sen olabilir her dosyayı işlemek zorunda sonra tam bir çöp toplama zorlamak gerektiğini test (dikkatli sonra) dava yapabilmek.

Diğer bir durum, bazı öğeleri işlemek için birkaç dakikada bir uyanan ve uykudayken herhangi bir durumu korumayan bir hizmettir . O zaman uyumadan hemen önce tam bir koleksiyon yapmaya zorlamak faydalı olabilir .

Bir koleksiyonu zorlamayı düşündüğüm tek zaman, yakın zamanda çok sayıda nesnenin oluşturulduğunu ve şu anda çok az nesneye başvurulduğunu bildiğim zamandır.

Kendim için bir GC zorlamak zorunda kalmadan bu tür şeyler hakkında ipuçları verebildiğimde bir çöp toplama API'sine sahip olmayı tercih ederim.

Ayrıca bkz. " Rico Mariani'nin Performans Haberleri "


2
Benzetme: Oyun okulu (sistem) boya kalemlerini (kaynakları) tutar. Çocuk sayısına (görevler) ve renk kıtlığına bağlı olarak, öğretmen (.Net) çocuklar arasında nasıl paylaşım yapılacağına ve paylaşılacağına karar verir. Nadir bir renk istendiğinde öğretmen havuzdan ayırabilir veya kullanılmayan bir renk arayabilir. Öğretmen, işleri düzenli tutmak (kaynak kullanımını optimize etmek) için kullanılmayan boya kalemlerini (çöp toplama) periyodik olarak toplama yetkisine sahiptir. Genellikle bir ebeveyn (programcı) sınıfta en iyi mum boya toplama politikasını önceden belirleyemez. Bir çocuğun planlanmış şekerlemesi, diğer çocukların boyamasına müdahale etmek için pek iyi bir an olmayacaktır.
AlanK

1
@AlanK, bunu seviyorum, çünkü çocuklar bir gün eve gittiklerinde, öğretmenler için yardımcılar için çocuklar araya girmeden iyi bir düzenleme yapmak için çok iyi bir zaman. (Üzerinde çalıştığım son sistemler, GC'yi zorlamak gibi zamanlarda hizmet sürecini yeni başlattı.)
Ian Ringrose

21

Rico Mariani tarafından verilen örneğin iyi olduğunu düşünüyorum : Uygulamanın durumunda önemli bir değişiklik varsa bir GC tetiklemek uygun olabilir. Örneğin, bir belge düzenleyicide, bir belge kapatıldığında bir GC'yi tetiklemek TAMAM olabilir.


2
Veya bir başarısızlık geçmişi gösteren ve ayrıntı düzeyini artırmak için etkili bir çözüm sunmayan büyük bir bitişik nesneyi açmadan hemen önce.
crokusek

17

Programlamada mutlak olan birkaç genel yönerge vardır. Çoğu zaman biri 'yanlış yapıyorsun' dediğinde, sadece belli miktarda dogma mırıldanıyorlar. C'de, kendi kendini değiştiren kod veya iş parçacıkları gibi şeylerden korkuyordu, GC dillerinde GC'yi zorluyor veya alternatif olarak GC'nin çalışmasını engelliyordu.

Çoğu kılavuzda ve iyi pratik kurallarda (ve iyi tasarım uygulamalarında) olduğu gibi, yerleşik norm etrafında çalışmanın mantıklı olduğu nadir durumlar vardır. Vakayı anladığınızdan, davanızın gerçekten genel uygulamadan vazgeçilmesini gerektirdiğinden ve neden olabileceğiniz riskleri ve yan etkileri anladığınızdan çok emin olmalısınız. Ama böyle durumlar var.

Programlama problemleri çok çeşitlidir ve esnek bir yaklaşım gerektirir. Çöp toplanan dillerde ve doğal olarak gerçekleşmesini beklemek yerine onu tetiklemenin mantıklı olduğu yerlerde GC'yi engellemenin mantıklı olduğu durumlar gördüm. Zamanın% 95'inde, bunlardan herhangi biri soruna doğru yaklaşmamış olmanın bir işareti olabilir. Ancak 20'de 1, muhtemelen bunun için yapılacak geçerli bir dava vardır.


12

Çöp toplama işinin üstesinden gelmemeyi öğrendim. Bununla birlikte, usingdosya G / Ç veya veritabanı bağlantıları gibi yönetilmeyen kaynaklarla uğraşırken anahtar kelimeyi kullanmaya devam ediyorum.


27
derleyici? derleyicinin GC ile ne ilgisi var? :)
KristoferA

1
Hiçbir şey, yalnızca kodu derler ve optimize eder. Ve bunun CLR ile hiçbir ilgisi yok ... hatta .NET ile bile.
Kon

1
Çok büyük resimler gibi çok fazla bellek kaplayan nesneler, açıkça çöp toplamadığınız sürece, çöp toplanmayabilir. Bence bu (büyük nesneler) OP'nin sorunu oldu, aşağı yukarı.
code4life

1
Kullanıma sarmak, kapsam kullanımdan çıktığında GC için planlanmasını sağlar. Bilgisayar patlamadıkça, bu bellek büyük olasılıkla temizlenecektir.
Kon

9

Bunun bir en iyi uygulama olup olmadığından emin değilim, ancak bir döngüde büyük miktarda görüntü ile çalışırken (yani, çok sayıda Grafik / Görüntü / Bitmap nesnesi oluşturup elden çıkarırken) düzenli olarak GC.Collect'e izin veriyorum.

Sanırım bir yerlerde GC'nin yalnızca program (çoğunlukla) boştayken çalıştığını ve yoğun bir döngünün ortasında çalışmadığını okudum, böylece manuel GC'nin mantıklı olabileceği bir alan gibi görünebilir.


Buna ihtiyacın olduğundan emin misin? GC , kodunuz boşta olmasa bile belleğe ihtiyaç duyarsa toplayacaktır.
Konrad Rudolph

Şimdi .net 3.5 SP1'de nasıl olduğundan emin değilim, ancak daha önce (1.1 ve 2.0'a karşı test ettiğime inanıyorum) bellek kullanımında bir fark yarattı. GC elbette her zaman ihtiyaç duyduğunuzda toplayacaktır, ancak yine de yalnızca 20'ye ihtiyacınız olduğunda 100 Megs RAM harcayabilirsiniz. Yine de birkaç test daha gerekir
Michael Stum

2
GC, "bir şey boşta olduğunda" değil, 0 nesli belirli bir eşiğe (örneğin 1MB) ulaştığında bellek tahsisinde tetiklenir. Aksi takdirde, nesneleri tahsis edip hemen atarak bir döngüde OutOfMemoryException ile sonuçlanabilirsiniz.
liggett78

5
100 megabayt RAM, başka bir işlem gerektirmezse boşa gitmez. Size güzel bir performans artışı sağlıyor :-P
Orion Edwards

9

Son zamanlarda manuel çağrıların gerekli GC.Collect()olduğu bir durum, küçük yönetilen C ++ nesnelerine sarılmış büyük C ++ nesneleriyle çalışırken, bunlar da C # üzerinden erişiliyordu.

Çöp toplayıcı hiçbir zaman çağrılmadı çünkü kullanılan yönetilen bellek miktarı önemsizdi, ancak kullanılan yönetilmeyen bellek miktarı çok fazlaydı. Dispose()Nesneleri manuel olarak çağırmak , nesnelere artık ihtiyaç duyulmadığında kendi başıma izlememi gerektirirken, arama GC.Collect()artık başvurulmayan nesneleri temizleyecektir .....


6
Bunu çözmenin daha iyi bir yolu, kurucuyu GC.AddMemoryPressure (ApproximateSizeOfUnmanagedResource)ve daha sonra GC.RemoveMemoryPressure(addedSize)sonlandırıcıyı aramaktır. Bu şekilde çöp toplayıcı, toplanabilecek yönetilmeyen yapıların boyutunu hesaba katarak otomatik olarak çalışacaktır. stackoverflow.com/questions/1149181/…
HugoRune

Ve sorunu çözmenin daha da iyi bir yolu, aslında yapmanız gereken Dispose () 'yi çağırmaktır.
fabspro

2
En iyi yol, Using yapısını kullanmaktır. Dene / Nihayet .Dispose bir güçlük
TamusJRoyce

7

En iyi uygulamayı zaten listelediğinizi ve GERÇEKTEN gerekli olmadıkça onu KULLANMAYIN. Önce bu soruları yanıtlamanız gerekiyorsa potansiyel olarak profil oluşturma araçlarını kullanarak kodunuza daha ayrıntılı bir şekilde bakmanızı şiddetle tavsiye ederim.

  1. Kodunuzda, öğeleri gerekenden daha geniş bir kapsamda bildiren bir şey var mı
  2. Bellek kullanımı gerçekten çok mu yüksek
  3. Gerçekten yardımcı olup olmadığını görmek için GC.Collect () kullanmadan önceki ve sonraki performansı karşılaştırın.

5

Programınızda bellek sızıntısı olmadığını, nesnelerin biriktiğini ve Gen 0'da GC-Edilemeyeceğini varsayalım, çünkü: 1) Uzun süre referans verildiği için Gen1 & Gen2'ye girin; 2) Büyük nesnelerdir (> 80K), bu nedenle LOH (Büyük Nesne Yığını) içine girin. Ve LOH, Gen0, Gen1 ve Gen2'de olduğu gibi sıkıştırma yapmaz.

".NET Belleğinin" performans sayacını kontrol edin, 1) sorunun gerçekten bir sorun olmadığını görebiliyor musunuz? Genel olarak, her 10 Gen0 GC, 1 Gen1 GC'yi ve her 10 Gen1 GC, 1 Gen2 GC'yi tetikleyecektir. Teorik olarak, GC0 üzerinde herhangi bir baskı yoksa (program belleği kullanımı gerçekten kabloluysa) GC1 ve GC2 asla GC-ed olamaz. Benim başıma asla gelmez.

Problem 2 için), LOH'nin şişip şişmediğini doğrulamak için ".NET Belleği" performans sayacını kontrol edebilirsiniz. Sorununuz için gerçekten bir sorunsa, bu blogun önerdiği gibi büyük bir nesne havuzu oluşturabilirsiniz http://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspx .


4

Büyük nesneler, gen 0'a değil, LOH'ye (büyük nesne yığını) tahsis edilir. Gen 0 ile çöp olarak toplanmadıklarını söylüyorsanız, haklısınız. Sadece tam GC döngüsü (0, 1 ve 2 nesilleri) gerçekleştiğinde toplandıklarına inanıyorum.

Bununla birlikte, diğer tarafta, büyük nesnelerle çalışırken ve hafıza baskısı artarken, GC'nin hafızayı daha agresif bir şekilde ayarlayıp toplayacağına inanıyorum.

Toplanıp toplanmayacağını ve hangi koşullarda olduğunu söylemek zor. Eskiden GC.Collect () 'i diyalog pencerelerini / formlarını sayısız kontrol vb. İle attıktan sonra yapardım (çünkü form ve kontrolleri birçok iş nesnesi örneği oluşturduğu / çok fazla veri yüklediği için gen 2'de sona erdiğinde - hayır büyük nesneler açıkça), ancak aslında uzun vadede herhangi bir olumlu veya olumsuz etki fark etmedi.


4

Bunu eklemek isterim: GC.Collect () (+ WaitForPendingFinalizers ()) 'ı çağırmak hikayenin bir parçasıdır. Başkaları tarafından haklı olarak belirtildiği gibi, GC.COllect () deterministik olmayan bir koleksiyondur ve GC'nin kendi takdirine (CLR) bırakılmıştır. WaitForPendingFinalizers'a bir çağrı ekleseniz bile, belirleyici olmayabilir. Bu msdn bağlantısından kodu alın ve 1 veya 2 olarak nesne döngüsü yinelemesiyle kodu çalıştırın. Belirsizliğin ne anlama geldiğini bulacaksınız (nesnenin yıkıcısında bir kırılma noktası ayarlayın). Bekle .. () tarafından yalnızca 1 (veya 2) kalan nesne varken yıkıcı çağrılmaz. [Alıntı gerekli.]

Kodunuz yönetilmeyen kaynaklarla (örn: harici dosya tanıtıcıları) ilgileniyorsa, yıkıcılar (veya sonlandırıcılar) uygulamanız gerekir.

İşte ilginç bir örnek:

Not : Yukarıdaki örneği MSDN'den zaten denediyseniz, aşağıdaki kod havayı temizleyecektir.

class Program
{    
    static void Main(string[] args)
        {
            SomePublisher publisher = new SomePublisher();

            for (int i = 0; i < 10; i++)
            {
                SomeSubscriber subscriber = new SomeSubscriber(publisher);
                subscriber = null;
            }

            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.WriteLine(SomeSubscriber.Count.ToString());


            Console.ReadLine();
        }
    }

    public class SomePublisher
    {
        public event EventHandler SomeEvent;
    }

    public class SomeSubscriber
    {
        public static int Count;

        public SomeSubscriber(SomePublisher publisher)
        {
            publisher.SomeEvent += new EventHandler(publisher_SomeEvent);
        }

        ~SomeSubscriber()
        {
            SomeSubscriber.Count++;
        }

        private void publisher_SomeEvent(object sender, EventArgs e)
        {
            // TODO: something
            string stub = "";
        }
    }

Önce çıktının ne olabileceğini analiz edin ve ardından çalıştırın ve ardından aşağıdaki nedeni okuyun:

{Yıkıcı, yalnızca program sona erdiğinde dolaylı olarak çağrılır. } Nesneyi belirleyici olarak temizlemek için, IDisposable uygulaması ve açık bir Dispose () çağrısı yapılmalıdır. Öz bu! :)


2

Bir şey daha, GC Collect'in açıkça tetiklenmesi programınızın performansını ARTIRMAYABİLİR. Daha da kötüleştirmek oldukça mümkün.

.NET GC iyi tasarlanmış ve uyarlanabilir olacak şekilde ayarlanmıştır, yani program belleği kullanımınızın "alışkanlığına" göre GC0 / 1/2 eşiğini ayarlayabilir. Böylece, bir süre çalıştıktan sonra programınıza uyarlanacaktır. GC.Collect'i açıkça çağırdığınızda, eşikler sıfırlanacaktır! Ve .NET, programınızın "alışkanlığına" yeniden adapte olmak için zaman harcamalıdır.

Önerim her zaman .NET GC'ye güvenmektir. Herhangi bir bellek sorunu yüzeyinde, ".NET Belleği" performans sayacını kontrol edin ve kendi kodumu tanılayın.


6
Sanırım bu yanıtı önceki cevabınızla birleştirmeniz daha iyi.
Salamander2007

1

En iyi uygulama olup olmadığından emin değilim ...

Öneri: emin olmadığınızda bunu veya herhangi bir şeyi uygulamayın. Gerçekler bilindiğinde yeniden değerlendirin, ardından doğrulamak için performans testlerinden önce / sonra gerçekleştirin.


0

Ancak, Collect () çağrısının olumsuz bir etkisi olmayacağını doğrulamak için kodunuzu güvenilir bir şekilde test edebilirseniz, devam edin ...

IMHO, bu "Programınızın gelecekte hiçbir zaman hataya sahip olmayacağını ispatlayabilirseniz, devam edin ..." demeye benzer.

Ciddiyetle, GC'yi zorlamak hata ayıklama / test etme amaçları için yararlıdır. Başka zamanlarda yapmanız gerektiğini düşünüyorsanız, ya yanılıyorsunuz ya da programınız yanlış oluşturulmuştur. Her iki durumda da çözüm GC'yi zorlamıyor ...


"o zaman ya yanılıyorsunuz ya da programınız yanlış oluşturulmuş. Her iki durumda da çözüm GC'yi zorlamıyor ..." Mutlaklar neredeyse her zaman doğru değildir. Mantıklı olan bazı istisnai durumlar var.
yenileniyor
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.