Çöp toplamaya zorlamak ne zaman iyidir?


135

Bu yüzden , C # çöp toplayıcısını neredeyse her bir cevabın aynı olduğu yerde çalıştırmaya zorlamakla ilgili bir soru okuyordum : bunu yapabilirsiniz, ancak yapmamalısınız - bazı çok nadir durumlar dışında . Ne yazık ki, orada kimse bu tür vakaların ne olduğunu ayrıntılandırmamaktadır.

Ne tür bir senaryoda çöp toplanmasının zorlanmasının iyi ya da makul bir fikir olduğunu söyleyebilir misiniz?

C # özel durumlar istemiyorum, bunun yerine, çöp toplayıcı olan tüm programlama dilleri. GC'yi Java gibi tüm dillerde zorlayamayacağınızı biliyorum, ancak yapabileceğinizi varsayalım.


17
"ama daha ziyade, bir çöp toplayıcıya sahip tüm programlama dilleri" Farklı diller (veya daha doğru bir şekilde farklı uygulamalar ), çöp toplama için farklı yöntemler kullanır; bu nedenle, tek bedene uyan bir kural bulamazsınız.
Albay Otuz İki

4
@Doval Gerçek zamanlı bir kısıtlama altındaysanız ve GC eşleşen garantiler sağlamıyorsa, bir kaya ile zor bir yer arasındasınız demektir. İstenmeyen duraklamaları ve hiçbir şey yapmamayı azaltabilir, ancak duyduğuma göre normal işlem sırasında tahsis etmekten kaçınmanın "daha kolay" olması.

3
Gerçek zamanlı zor zaman tarihlerine sahip olmanız beklenirse, asla GC dilini asla kullanmayacağınız izlenimindeydim.
GregRos

4
Buna VM'ye özgü olmayan bir şekilde nasıl cevap verebileceğinizi göremiyorum. 64 bit işlemlerle ilgili olmayan 32 bit işlemlerle ilgili. .NET JVM ve üst seviye bir için
rwong

3
@DavidConrad C # ile zorlayabilirsiniz. Bu yüzden soru.
Omega

Yanıtlar:


127

Tüm GC uygulamalarını kullanmanın uygun bir yolu hakkında battaniye ifadeleri yapamazsınız . Onlar çılgınca değişir. Bu yüzden başlangıçta bahsettiğiniz .NET ile konuşacağım.

Herhangi bir mantık veya sebeple bunu yapmak için GC'nin davranışını oldukça yakından bilmeniz gerekir.

Koleksiyonda verebileceğim tek tavsiye şudur: Asla yapma.

GC'nin karmaşık ayrıntılarını gerçekten biliyorsanız, tavsiyeme gerek kalmaz, bu yüzden önemli olmaz. Zaten% 100 güvenle bilmiyorsanız o yardımcı ve çevrimiçi bakmak zorunda ve böyle bir cevap bulacaksınız: Sen GC.Collect çağırarak edilmemelidir veya alternatif: Sen nasıl GC eserlerin ayrıntılarını öğrenmek gitmeli içte ve dışta, ancak o zaman cevabı bileceksin .

O GC.Collect kullanmak için bazı mantıklı tek bir güvenli yer yoktur :

GC.Collect, işlerin zamanlamasını belirlemek için kullanabileceğiniz bir API'dir. Bir algoritmayı profillendirebilir, derhal başka bir algoritmayı profillendirebilir ve ardından birincisini algılayarak ilk algonun GC'sinin oluşmadığını bilirsiniz.

Bu tür bir profilleme, herhangi birisine manuel olarak toplama önerebileceğim tek zaman.


Yine de İçtiğimiz Örnek

Muhtemel bir kullanım durumu, eğer gerçekten büyük şeyler yüklerseniz, doğrudan Gen 2'ye gidecek olan Büyük Nesne Yığınına gireceklerdir, fakat yine de Gen 2, daha az sıklıkla topladığı için uzun ömürlü nesneler içindir. Herhangi bir nedenle kısa ömürlü nesneleri Gen 2'ye yüklediğinizi biliyorsanız , Gen 2'nizi daha küçük tutmak ve koleksiyonlarını daha hızlı tutmak için bunları daha çabuk temizleyebilirsiniz.

Bu, bulabileceğim en iyi örnek ve bu iyi değil - burada inşa ettiğiniz LOH baskısı daha sık koleksiyonlara neden olur ve koleksiyonlar olduğu kadar sıktır - olasılıklar LOH'yi olduğu gibi temizliyor olabilir geçici cisimlerle patlatırken hızlı. Ben sadece güvenmiyorum kendime uzak insanlar tarafından ayarlanan - GC kendisinden daha iyi bir toplama frekansı tahmin etmek çok benden daha akıllı


Öyleyse, .NET GC'deki bazı anlambilim ve mekanizmalardan bahsedelim ... veya ..

.NET GC hakkında bildiğim her şeyi düşünüyorum

Lütfen, burada hataları bulan herkes - beni düzeltir. GC'nin çoğunun kara büyü olduğu iyi biliniyor ve belirsiz olduğum detayları bırakmaya çalışırken, muhtemelen hala bazı şeyler yanlış.

Aşağıda kesin olarak bilmediğim çok daha fazla bilginin yanı sıra, emin olamadığım çok sayıda ayrıntı eksik. Bu bilgileri kullanmak kendi sorumluluğunuzdadır.


GC Kavramları

.NET GC tutarsız zamanlarda gerçekleşir, bu nedenle "deterministik olmayan" olarak adlandırılır, bu, belirli zamanlarda gerçekleşmesine güvenemeyeceğiniz anlamına gelir. Aynı zamanda bir kuşak çöp toplayıcısıdır, bu da nesnelerinizi kaç tane GC geçidi yaşadıklarına ayırdığı anlamına gelir.

Gen 0 yığınındaki nesneler 0 koleksiyonla yaşamıştır, bunlar yeni yapılmıştı, son zamanlarda bunlar hazır olmalarından bu yana hiçbir toplama gerçekleşmedi. Gen 1 yığınınızdaki nesneler bir koleksiyon geçidinde yaşamıştır ve aynı şekilde Gen 2 yığınınızdaki nesneler de 2 koleksiyon geçidinde yaşamıştır.

Şimdi bu belirli nesiller ve bölümleri buna göre nitelendirmesinin nedenini belirtmeye değer. .NET GC yalnızca bu üç nesli tanır, çünkü bu üç küme üzerinden geçen koleksiyonun tümü biraz farklıdır. Bazı nesneler koleksiyondan binlerce kez geçerek hayatta kalabilir. GC bunları yalnızca Gen 2 yığın bölümünün diğer tarafında bırakır, aslında Gen 44 olduklarından onları başka hiçbir yerde bölümlemenin bir anlamı yoktur; Üzerlerindeki koleksiyon geçişi Gen 2 yığınındaki her şey ile aynı.

Bu belirli nesiller için anlamsal amaçlar ve bunları onurlandıran mekanizmalar uygulandı ve bir dakika içinde bunlara ulaşacağım.


Koleksiyonda neler var

Bir GC koleksiyonu geçidinin temel konsepti, bu nesnelere hala canlı referanslar (GC kökleri) olup olmadığını görmek için bir yığın alanındaki her nesneyi kontrol etmesidir. Bir nesne için bir GC kökü bulunursa, bu şu anda kod yürütme kodunun hala o nesneye erişip kullanabileceği anlamına gelir, bu nedenle silinemez. Bununla birlikte, bir nesne için bir GC kökü bulunmazsa, bu, çalışan işlemin artık nesneye ihtiyacı olmadığı anlamına gelir, böylece yeni nesneler için belleği boşaltmak için onu kaldırabilir.

Şimdi bir grup nesneyi temizledikten ve bazılarını yalnız bıraktıktan sonra, talihsiz bir yan etki ortaya çıkacak: Ölülerin kaldırıldığı canlı nesneler arasında boş alan var. Yalnız bırakılırsa bu hafıza parçalanması yalnızca hafızayı boşa harcar, böylece koleksiyonlar genellikle "canlılık" denilen şeyi yaparlar, burada tüm canlı nesneleri sola alırlar ve yığın içinde bir araya getirirler, böylece boş hafıza Gen için yığının bir tarafında bitişik olur 0.

Şimdi 3 yığın bellek fikri göz önüne alındığında, hepsi yaşadıkları koleksiyon geçişlerinin sayısına göre bölümlere ayrılarak, bu bölümlerin neden var olduğu hakkında konuşalım.


Gen 0 Koleksiyonu

Gen 0 mutlak en yeni nesnelerdir, çok küçük olma eğilimindedir - bu yüzden güvenle sıkça toplayabilirsiniz . Sıklık, yığının küçük kalmasını ve koleksiyonların çok hızlı olmasını sağlar çünkü bu kadar küçük bir yığının üzerinde toplanırlar. Bu, aşağı yukarı iddia edilen bir buluşsal görüşe dayanmaktadır: Oluşturduğunuz , çok geçici olan geçici nesnelerin büyük bir kısmı , bu nedenle geçici olarak kullanıldıktan hemen sonra kullanılamaz veya referans alınmayacak ve bu nedenle toplanabilir.


Gen 1 Koleksiyonu

Gen 1, bu çok geçici nesneler kategorisine girmeyen nesneler olmakla birlikte hala kısa ömürlü olabilir, çünkü yaratılan nesnelerin büyük bir kısmı uzun süre kullanılmaz. Bu nedenle Gen 1 oldukça sık toplar, yine yığınını küçük tutar, böylece koleksiyonlar hızlı olur. Ancak, varsayım nesnelerinin daha az olduğu Gen 0'dan daha geçicidir, bu nedenle Gen 0'dan daha az toplanır.

Açıkça söyleyeceğim: Gen 0'ların koleksiyon geçişi ile Gen 1'leri arasında farklılık gösteren teknik mekanizmaları bilmiyorum, topladıkları sıklıktan başka hiç bir şey yoksa.


Gen 2 Koleksiyonu

Gen 2 şimdi tüm yığınların annesi olmalı, değil mi? Evet, bu az çok doğru. Tüm kalıcı nesnelerinizin yaşadığı yer - Main()mesela hayatınızdaki nesne ve Main()referansınız olan her şey, çünkü bunlar Main()sürecinizin sonunda geri dönene kadar kökleşecektir .

Gen 2'nin temelde diğer nesillerin toplayamadığı her şey için bir kova olduğu düşünüldüğünde, nesneler büyük ölçüde kalıcıdır veya en azından uzun süre yaşadılar. Yani Gen 2’de olanların çok azını tanımak aslında toplanabilecek bir şey olacak, sık sık toplanması gerekmiyor. Bu, koleksiyonunun daha yavaş çalışmasını sağlar çünkü çok daha az sıklıkta çalışır. Yani bu temelde garip senaryolar için tüm ekstra davranışları takip ettikleri yer, çünkü onları yürütecek zamanları var.


Büyük Nesne Yığını

Gen 2'nin ekstra davranışlarına bir örnek, aynı zamanda Büyük Nesne Yığınında da toplama yaptığıdır. Şimdiye kadar tamamen Küçük Nesne Yığını hakkında konuşuyordum, ancak .NET çalışma zamanı, yukarıda sıkıştırma olarak adlandırdığım şey nedeniyle belirli boyutlardaki şeyleri ayrı bir yığına ayırıyor. Sıkıştırma, koleksiyonlar Küçük Nesne Yığınında bittiğinde hareket etmeyi gerektirir. Gen 1'de yaşayan 10 MB'lık bir nesne varsa, koleksiyondan sonra sıkıştırmayı tamamlaması çok daha uzun sürecek ve böylece Gen 1'in koleksiyonunu yavaşlatacak. Böylece 10 MB'lık nesne Büyük Nesne Yığına tahsis edilir ve çok nadir çalışan Gen 2 sırasında toplanır.


Sonlandırma

Başka bir örnek, sonlandırıcılar içeren nesnelerdir. Sonlandırıcıyı .NET'lerin GC (yönetilmeyen kaynaklar) kapsamı dışındaki kaynaklara başvuran bir nesneye koyuyorsunuz. Sonlandırıcı, GC'nin yönetilmeyen bir kaynak toplanmasını talep etmesinin tek yoludur - işleminizden sızmaması için yönetilmeyen kaynağın elle toplanması / kaldırılması / bırakılması için sonlandırıcınızı uygularsınız. GC nesnelerinizi sonlandırıcıyı gerçekleştirmeye başladığında, uygulamanız yönetilmeyen kaynağı temizler ve böylece GC'nin bir kaynak sızıntısı riski olmadan nesnenizi kaldırabilmesini sağlar.

Sonlandırıcıların bunu yaptığı mekanizma doğrudan bir sonlandırma kuyruğunda referans olarak alınmaktadır. Çalışma zamanı bir sonlandırıcıyla bir nesne tahsis ettiğinde, o nesneye sonlandırma sırasına bir işaretçi ekler ve nesnenizi yerine kilitler (sabitleme adı verilir), böylece sıkıştırma, sonlandırma sırası başvurusunu bozacak şekilde taşımaz. Toplama geçişleri gerçekleştikçe, sonunda nesneniz artık bir GC köküne sahip olmayacaktır, ancak sonlandırma toplanmadan önce gerçekleştirilmelidir. Öyleyse, nesne öldüğünde, koleksiyon sonlandırma kuyruğundan referansını hareket ettirecek ve "FReachable" kuyruğu olarak bilinen şeyin üzerine bir referans yerleştirecektir. Sonra koleksiyon devam ediyor. Gelecekte bir başka “deterministik olmayan” zamanda, Sonlandırıcı iş parçacığı olarak bilinen ayrı bir iş parçacığı, başvurulan nesnelerin her biri için sonlandırıcıları yürüten, Yenilebilir sıradan geçecektir. Bitirdikten sonra, FReachable kuyruğu boştur ve sonlandırmaya gerek duymadıklarını söyleyen her nesnenin başlığında bir miktar çevrilmiş (Bu bit elle de çevrilebilir)GC.SuppressFinalize( Dispose()yöntemlerde yaygın olan ), ayrıca nesnelerin kaldırıldığından şüpheleniyorum , ancak benden alıntı yapma. Bu nesnenin içinde bulunduğu yığınla ilgili bir sonraki koleksiyon sonunda toplanacak. Gen 0 koleksiyonları, bu sonlandırmaya ihtiyaç duyulan biti olan nesnelere bile dikkat etmiyor, köklerini kontrol etmeden otomatik olarak onları tanıtıyor. Gen 1'de sonlandırılması gereken köksüz bir nesne FReachablekuyruğa atılacak, ancak koleksiyon bununla başka bir şey yapmaz, bu nedenle Gen 2'de yaşar. Bu şekilde, sonlandırıcıya sahip olan ve olmayan tüm nesneler GC.SuppressFinalizeGen 2'de toplanacak.


4
@FlorianMargaine evet ... tüm uygulamalar arasında "GC" hakkında bir şey söylemek gerçekten mantıklı gelmiyor ..
Jimmy Hoffa

10
tl; dr: Bunun yerine nesne havuzlarını kullanın.
Robert Harvey,

5
tl; dr: Zamanlama / profil oluşturma için yararlı olabilir.
kutschkem

3
@Mekaniklerin üzerindeki açıklamamı okuduktan sonra (onları anladığım gibi), gördüğünüz yarar ne olurdu? Çok sayıda nesneyi temizlediniz - SOH'de (veya LOH?)? Bu koleksiyon için diğer konuların durmasına neden mi oldunuz? Bu koleksiyon, Gen 2'ye temizlediklerinden iki kat daha fazla nesne tanıttı mı? Koleksiyon, LOH'da sıkışmaya neden oldu mu (açtınız mı?)? Kaç tane GC yığınınız var ve GC'niz sunucu veya masaüstü modunda mı? GC bir çılgın buz berg, ihanet suların altında. Sadece temizle. Rahatça toplanacak kadar akıllı değilim.
Jimmy Hoffa

4
@RobertHarvey Nesne havuzları da gümüş mermi değildir. Çöp toplayıcısının jenerasyonu 0 zaten etkin bir nesne havuzudur - genellikle en küçük önbellek seviyesine uyacak şekilde boyutlandırılmıştır ve bu nedenle yeni nesneler genellikle önbellekte olan bellekte oluşturulur. Nesne havuzunuz artık GC'nin önbellek çocuk odasına karşı yarışıyor ve GC'nin çocuk odası ve havuzunuzun toplamı önbellekten büyükse, açıkça önbellek özlemleri olacaktır. Paralelliği kullanmayı planlıyorsanız, şimdi senkronizasyonu yeniden uygulamanız ve yanlış paylaşım konusunda endişelenmeniz gerekir.
Doval

68

Ne yazık ki, orada kimse bu tür vakaların ne olduğunu ayrıntılandırmamaktadır.

Bazı örnekler vereceğim. Sonuçta, bir GC'yi zorlamanın iyi bir fikir olduğu nadirdir, ancak buna tamamen değebilir. Bu cevap, .NET ve GC literatüründeki tecrübelerime dayanıyor. Diğer platformlara iyi bir şekilde genelleştirmelidir (en azından önemli bir GC'ye sahip olanlar).

  • Çeşitli türde kıyaslamalar . Bir kıyaslama başladığında bilinen bir yönetilen yığın durumu istersiniz, böylece GC kriterler sırasında rastgele tetiklemez. Bir testi tekrarladığınızda, her bir tekrarlamada aynı sayıda ve aynı miktarda GC çalışmasını istersiniz.
  • Kaynakların ani serbest bırakılması. Örneğin, önemli bir GUI Penceresini kapatmak veya bir önbelleği yenilemek (ve böylece eski potansiyel olarak büyük önbellek içeriğini serbest bırakmak). GC bunu algılayamaz çünkü yaptığınız tek şey null değerine bir referans ayarlamaktır. Bu yetimlerin tamamıyla bir nesne grafiğinin gerçeği kolayca tespit edilemez.
  • Sızan yönetilmeyen kaynakların serbest bırakılması . Elbette bu asla gerçekleşmemeli, ancak bir 3. parti kütüphanesinin (sızan nesneler gibi) sızdığı durumlar gördüm. Geliştirici bazen bir koleksiyon oluşturmaya zorlandı.
  • Oyunlar gibi etkileşimli uygulamalar . Oyun sırasında oyunlar kare başına çok katı zaman bütçelerine sahiptir (kare başına 60Hz => 16ms). Kaldırmalardan kaçınmak için GC'lerle başa çıkmak için bir stratejiye ihtiyacınız var. Böyle bir strateji G2 GC'lerini mümkün olduğunca geciktirmek ve onları bir yükleme ekranı veya kesim sahnesi gibi uygun bir zamanda zorlamaktır. GC, bu en iyi anın ne zaman olduğunu bilemez.
  • Genel olarak gecikme kontrolü . Bazı web uygulamaları GC'leri devre dışı bırakır ve yük dengeleyici rotasyonundan çıkarılırken periyodik olarak bir G2 koleksiyonu çalıştırır. Bu şekilde G2 gecikmesi hiçbir zaman kullanıcıya yüzeylenmez.

Amacınız iş hacmindeyse, GC ne kadar nadir olursa o kadar iyidir. Bir koleksiyona zorlamak gibi durumlarda, olumlu bir etkisi olamaz (canlı olanlara serpiştirilmiş ölü nesneleri kaldırarak CPU önbellek kullanımını artırmak gibi oldukça tartışılan konular hariç). Parti toplama, bildiğim tüm koleksiyoncular için daha verimlidir. Sabit durumlu bellek tüketimindeki üretim uygulamaları için, bir GC'yi indüklemek yardımcı olmuyor.

Yukarıda verilen örnekler, bellek kullanımının tutarlılığını ve sınırlılığını hedeflemektedir. Bu durumlarda indüklenmiş GC'ler anlamlı olabilir.

GC'nin, gerçekten en uygun olduğu zaman bir koleksiyona neden olan ilahi bir varlık olduğu geniş çaplı bir fikir gibi görünüyor. Bildiğim hiçbir GC bu karmaşık değil ve gerçekten de GC için optimal olmak çok zor. GC, geliştiriciden daha az şey biliyor. Sezgisel tarama bellek sayaçlarına ve toplama hızı vb. Şeylere dayanmaktadır. Sezgisel tarama genellikle iyidir, ancak büyük miktarda yönetilen belleğin serbest bırakılması gibi uygulama davranışında ani değişiklikler yakalamaz. Ayrıca, yönetilmeyen kaynaklara ve gecikme gereksinimlerine de kördür.

GC maliyetlerinin yığın büyüklüğüne ve yığıntaki referans sayısına göre değiştiğini unutmayın. Küçük bir yığında maliyet çok küçük olabilir. 1GB yığın boyutundaki bir üretim uygulamasında .NET 4.5 ile 1-2 GB / sn'lik G2 toplama oranlarını gördüm.


Gecikme kontrol durumu için, bunu periyodik olarak yapmak yerine, ihtiyaç duyduğunuz şekilde de yapabilirsiniz (yani hafıza kullanımı belli bir eşiğin üzerine çıktığında).
Paŭlo Ebermann

3
İkinci ila son paragraf için +1. Bazı insanlar derleyiciler hakkında aynı düşünceye sahiptir ve hemen hemen her şeyi "erken optimizasyon" olarak adlandırırlar. Onlara genellikle benzer bir şey söylerim.
Honza Brabec 20:15

2
Bu paragraf için de +1. İnsanların, başkası tarafından yazılmış bir bilgisayar programının, programlarının performans özelliklerini kendisinden daha iyi anlamaları gerektiğini düşünmelerini şok edici buluyorum.
Mehrdad

1
@HonzaBrabec Sorun her iki durumda da aynı: GC veya derleyiciden daha iyi bildiğinizi düşünüyorsanız , o zaman kendinize zarar vermek çok kolaydır. Aslında daha fazlasını biliyorsanız, yalnızca erken olmadığını bildiğiniz zaman optimizasyon yaparsınız.
svick

27

Genel bir ilke olarak, bir çöp toplayıcı "bellek baskısı" ile karşılaştığında toplanacaktır ve programın yürütülmesinde performans sorunlarına veya hatta gözle görülür duraklamalara neden olabileceğinden, diğer zamanlarda toplanmaması iyi bir fikirdir. Ve aslında, ilk nokta ikinciye dayanıyor: bir nesil çöp toplayıcı için, en azından, daha iyi çalışır, böylece çöpün iyi nesnelere oranı artar, böylece programı duraklatmak için harcanan zamanı en aza indirmek için , ertelemek ve çöplerin olabildiğince yığılmasına izin vermek zorundadır.

Öyleyse, çöp toplayıcıyı elle çağırmak için uygun zaman, 1) çok fazla çöp oluşturmuş olması muhtemel olan bir şey yapmayı bitirdiğinizde ve 2) kullanıcının biraz zaman alması ve sistemi tepkisiz bırakması beklenir. neyse. Klasik bir örnek, büyük bir şeyin yüklenmesinin sonundadır (belge, model, yeni bir seviye, vb.)


12

Kimsenin bahsetmediği şeylerden biri, Windows GC'nin şaşırtıcı derecede iyi olmasına rağmen, Xbox'taki GC'nin çöp olduğu (punto amaçlı) .

Bu nedenle, XBox üzerinde çalışacak bir XNA oyununu kodlarken, zaman kazanmak için çöp toplama işlemi çok önemlidir, yoksa korkunç aralıklı FPS hıçkırıklarına sahip olacaksınız. Ek olarak, XBox'ta structçöp toplanması gereken nesne sayısını en aza indirgemek için normalde olduğundan çok daha sık kullanmanız yaygındır .


4

Çöp toplama her şeyden önce bir bellek yönetim aracıdır. Bu nedenle, çöp toplayıcıları bellek baskısı olduğunda toplanacaktır.

Modern çöp toplayıcıları çok iyi ve daha iyi oluyorlar, bu yüzden elle toplayarak daha iyi hale getirme ihtimaliniz yok. Bugün bir şeyleri iyileştirebilseniz bile, seçtiğiniz çöp toplayıcısındaki gelecekteki bir iyileştirmenin optimizasyonunuzu etkisiz veya hatta verimsiz hale getirmesi olabilir.

Ancak , çöp toplayıcıları genellikle bellek dışındaki kaynakların kullanımını en iyi duruma getirme girişiminde bulunmaz. Çöp toplanan ortamlarda, çoğu değerli bellek dışı kaynakların bir closeyöntemi veya benzerleri vardır, ancak varolan bir API ile uyumluluk gibi nedenlerden dolayı durum böyle olmadığı durumlar vardır.

Bu gibi durumlarda, değerli bir bellek dışı kaynağın kullanıldığını bildiğiniz zaman çöp toplama işlemini el ile başlatmak mantıklı olabilir.

RMI

Bunun somut bir örneği Java'nın Remote Method Invocation'dır. RMI uzak yordam çağrısı kütüphanesidir. Genellikle, istemciler tarafından kullanılmak üzere çeşitli nesneleri kullanılabilir kılan bir sunucunuz vardır. Bir sunucu bir nesnenin herhangi bir müşteri tarafından kullanılmadığını biliyorsa, o nesne çöp toplama için uygundur.

Bununla birlikte, sunucunun bunu bilmesinin tek yolu, istemci bunu söylerse olur ve istemci, istemci ne kullanıyorsa topladığında bir nesneye ihtiyaç duymayacağını söyler.

Bu, bir sorun yaratır, çünkü müşteri çok fazla boş hafızaya sahip olabilir, bu nedenle çöp toplama işlemlerini çok sık çalıştırmayabilir. Bu arada, sunucunun bellekte çok sayıda kullanılmamış nesnesi olabilir, toplayamadığı için istemcinin bunları kullanmadığını bilmediğinden.

RMI'deki çözüm, istemcinin, çok fazla belleği olsa bile, nesnelerin sunucuda derhal toplanmasını sağlamak için düzenli aralıklarla çöp toplama işlemi yapmasıdır.


"Bu durumlarda, değerli bir bellek olmayan kaynağın kullanıldığını bildiğinizde, çöp toplama işlemini el ile başlatmak mantıklı olabilir" - bellek dışı bir kaynak kullanılıyorsa, bir usingblok kullanmalı veya başka bir Closeyöntemle çağırmalısınız . kaynağın en kısa sürede atıldığından emin olun. Bellek dışı kaynakları temizlemek için GC'ye güvenmek güvenilmezdir ve her türlü soruna neden olur (özellikle erişim için kilitlenmesi gereken dosyalarda yalnızca bir kez açılabilir).
Jules

Yanıtta belirtildiği gibi, bir closeyöntem mevcut olduğunda (veya kaynak bir usingblokla birlikte kullanılabilirse ), bunlar doğru yaklaşımdır. Cevap, özellikle bu mekanizmaların mevcut olmadığı nadir durumlarla ilgilidir.
James_pic

Benim kişisel görüşüm olmayan bir bellek kaynağı yöneten ancak herhangi bir arayüz olduğunu gelmez yakın yöntem sağlar bir arayüz olduğunu kullanılmamalıdır güvenilir kullanmak için bir yolu yoktur, çünkü.
Jules

@Jule Katılıyorum, ama bazen kaçınılmaz. Bazen soyutlamalar sızıntı yapar ve sızan bir soyutlama kullanmak, soyutlama yapmamaktan daha iyidir. Bazen, tutamayacağınızı bildiğinize dair söz vermenizi gerektiren eski kodlarla çalışmanız gerekir. Evet, nadirdir ve mümkünse kaçınılması gerekir ve çöp toplanmasının zorlanmasına ilişkin tüm bu uyarıların olmasının bir nedeni vardır, ancak bu durumlar ortaya çıkar ve OP bu durumların nasıl göründüğünü soruyordu - ki cevap verdim .
James_pic,

2

En iyi uygulama, çoğu durumda çöp toplamaya zorlamamaktır. (Üzerinde çalıştığım her sistem, çöp toplamalarına zorlanmış, çözülmüşse çöp toplama zorunluluğunu ortadan kaldıracak ve sistemi büyük ölçüde hızlandıracak sorunların altını çiziyordu.)

Bir var birkaç durum olduğunda size daha sonra çöp toplayıcı yapar bellek kullanımı hakkında daha fazla bilgi. Bu, çok kullanıcılı bir uygulamada veya bir kerede birden fazla isteği yanıtlayan bir hizmette doğru olması muhtemel değildir.

Ancak bazı parti tipi işlemlerde GC'den daha fazla şey biliyorsunuzdur. Örneğin bir uygulamayı düşünün.

  • Komut satırında dosya adlarının bir listesi verilir
  • Tek bir dosyayı işler sonra sonucu bir sonuç dosyasına yazar.
  • Dosyayı işlerken, dosyanın işlenmesi tamamlanıncaya kadar toplanamayan birçok birbirine bağlı nesne oluşturur (örneğin bir ayrıştırma ağacı)
  • İşlediği dosyalar arasında eşleşme durumunu tutmaz .

Sen olabilir Eğer sürecini her dosyayı aldıktan sonra tam bir çöp toplama zorlamak gerektiğini test (dikkatli sonra) dava yapabilmek.

Başka bir örnek, bazı maddeleri işlemek için birkaç dakikada bir uyanan ve uykuda iken herhangi bir durumu sürdürmeyen bir hizmettir . O zaman tam bir koleksiyon uyumadan hemen önce zorlamak faydalı olabilir .

Bir koleksiyonu zorlamayı düşüneceğim tek zaman, son zamanlarda çok fazla nesnenin yaratıldığını ve şu anda çok az sayıda nesneye başvuru yapıldığını bildiğim zamandı.

Bir GC'yi kendim zorlamak zorunda kalmadan bu tür şeyler hakkında ipucu verebildiğimde bir çöp toplama API'sına sahip olmayı tercih ederim.

Ayrıca bakınız " Rico Mariani'nin Performance Tidbits "


2

Gc () 'ı kendiniz aramak isteyebileceğiniz bazı durumlar vardır.

  • [ Bazı insanlar bunun iyi olmadığını söylüyor çünkü nesneleri daha eski kuşak mekanlara tanıtabiliyor, ki bunun iyi bir şey olmadığını kabul ediyorum. Ancak, her zaman terfi ettirilebilecek nesneler olacağı her zaman doğru DEĞİLDİR . Bu gc()çağrıdan sonra , çok az sayıda nesnenin eski kuşak alana taşınmasına izin verilmesi kesinlikle mümkündür ] Büyük bir nesne koleksiyonu yaratacak ve çok miktarda bellek kullanacaksınız. Basitçe, mümkün olduğunca hazırlık alanı kadar alanı temizlemek istiyorsunuz. Bu sadece sağduyu. gc()El ile çağırarak , belleğe yüklediğiniz büyük nesne koleksiyonunun bir kısmında fazladan referans grafiği kontrolü yapılmayacaktır. Kısacası, gc()çok fazla hafızaya yüklemeden önce koşarsanız,gc() yükleme sırasında indüklenen en az bir defa daha az olur, yükleme sırasında hafıza basıncı yaratılır.
  • Büyük bir koleksiyon yüklemeyi bitirdiğinizdebüyüknesneleri ve belleğe daha fazla nesne yüklemeyeceksiniz. Kısacası, faz oluşturmaktan fazı kullanmaya geçiyorsunuz. gc()Uygulamaya bağlı olarak çağrıldığında , kullanılan bellek, önbellek konumunu büyük ölçüde artıran sıkıştırılır. Bu, profilden alamayacağınız performansta büyük iyileşme sağlayacaktır .
  • Birincisine benzer, ancak siz yaparsanız gc()ve hafıza yönetimi uygulamasını destekliyorsa, fiziksel hafızanız için daha iyi bir süreklilik yaratacağınız görüşüne göre . Bu yine yeni büyük nesne koleksiyonunu daha sürekli ve kompakt hale getirir, bu da performansı artırır

1
Birisi olumsuz oylamanın nedenini gösterebilir mi? Ben cevabı yargılayacak kadar bilgim yok (ilk bakışta bana mantıklı geliyor).
Omega

1
Sanırım üçüncü nokta için bir oy hakkın var. Potansiyel olarak "Bu sadece sağduyulu" demek için de.
immibis

2
Büyük bir nesne koleksiyonu oluşturduğunuzda GC, bir koleksiyonun gerekli olup olmadığını bilmek için yeterince akıllı olmalıdır. Hafızanın sıkıştırılması gerektiğinde aynıdır. İlgili nesnelerin hafıza yerini optimize etmek için GC'ye güvenmek, güvenilir görünmemektedir. Başka çözümler bulabileceğinizi düşünüyorum (yapı, güvensiz, ...). (Ben düşürücü değilim).
Guillaume

3
İyi vakit geçirme konusundaki ilk fikriniz bence sadece kötü bir tavsiye. Son zamanlarda bir koleksiyonun bulunma olasılığı yüksektir, bu yüzden tekrar toplamaya teşebbüsünüz objeleri keyfi bir şekilde sonraki nesillere teşvik edecektir, ki bu neredeyse her zaman kötüdür. Daha sonraki nesiller, başlaması daha uzun süren koleksiyonlara sahiptir, yığın boyutlarını "mümkün olduğunca fazla alan boşaltmak" için arttırmak, bunun daha problemli olmasına neden olur. Ayrıca, bir yük ile hafıza basıncını artırmak üzereyseniz, yine de koleksiyonları başlatmaya başlayacaksınız, ki bu daha yavaş çalışacaktır çünkü Gen1 / 2
Jimmy Hoffa

2
By calling gc() depending on implementation, the memory in used will be compacted which massively improves cache locality. This will result in massive improve in performance that you will not get from profiling.Üst üste tonlarca nesne ayırırsanız, oranlar zaten sıkıştırılmıştır. Herhangi bir şey varsa, çöp toplama onları hafifçe karıştırır. Her iki durumda da, yoğun ve rasgele zıplamayan veri yapılarını bellekte kullanmak daha büyük bir etkiye sahip olacak. Saf bir düğüm başına bir öğe bağlantılı listesi kullanıyorsanız, bunun için hiçbir manüel GC hilesi telafi etmez.
Doval

2

Gerçek dünyadan bir örnek:

Nadiren değişen ve çok hızlı bir şekilde erişilmesi gereken (AJAX ile tuşa basma başına cevap için yeterince hızlı) çok büyük bir veri setini kullanan bir web uygulamasına sahiptim.

Burada yapılacak açık bir şey, ilgili grafiği belleğe yüklemek ve veri tabanı yerine oradan erişmek ve DB değiştiğinde grafiği güncellemektir.

Ancak, çok büyük olmak üzere, naif bir yük, gelecekte büyümeye bağlı olarak verilerle en az 6 GB bellek kapardı. (Kesin rakamlara sahip değilim, bir keresinde 2GB makinemin en az 6 GB ile başa çıkmaya çalıştığı belliydi, işe yaramayacağını bilmek için gereken tüm ölçümleri aldım).

Neyse ki, bu veri kümesinde birbirleriyle aynı olan çok sayıda buz gibi değişmez nesne vardı; Belli bir partinin başka bir partiyle aynı olduğunu düşündüğümde, bir referansın diğerine atıfta bulunabildiğim için birçok veri toplanabiliyordu ve bu yüzden her şeyi yarım konserin altına sığdırabiliyordum.

Her şey yolunda ve güzel, ama bunun için hala bu duruma ulaşmak için yaklaşık yarım dakika uzayda 6GB'tan fazla nesneyi çalkalayarak. Kendi kendine bırakılan GC, baş edemedi; Uygulamanın olağan paterni üzerindeki aktivitedeki ani (saniyedeki serbest bırakmalara göre çok daha az ağırdı) çok keskindi.

Bu nedenle, GC.Collect()bu derleme sürecinde periyodik olarak çağrı yapmak , her şeyin sorunsuz çalıştığı anlamına geliyordu. Tabii ki, GC.Collect()uygulamanın çalıştığı sürenin geri kalanını manuel olarak aramadım.

Bu gerçek dünya olgusu, ne zaman kullanmamız gerektiğine dair kılavuz ilkelere iyi bir örnektir GC.Collect():

  1. Koleksiyon için mevcut olan nispeten nadir sayıda nesne kullanıldığında kullanım (değerli megabaytlar elde edildi ve bu grafik oluşturma, uygulamanın ömrü boyunca çok nadir görülen bir durumdu (haftada bir dakika).
  2. Performanstaki bir kayıp göreceli olarak tolere edilebilir olduğunda yapın; bu sadece uygulama başlangıcında oldu. (Bu kuralın bir başka güzel örneği, bir oyun sırasındaki seviyeler veya oyuncuların biraz duraklamadığı bir oyundaki diğer noktalar arasındadır).
  3. Profil gerçekten bir gelişme olduğundan emin olmak için. (Oldukça kolay; "Çalışıyor" neredeyse her zaman atıyor "çalışmıyor").

Ben bir durum var diye düşündüm Çoğu zaman GC.Collect()noktası 1 ve 2 uygulamalı, çünkü çağırarak değer, nokta 3 daha kötü ya da en azından yapılan işler daha iyi (ve istiyorum çok az veya hiç iyileşme şeyler yapılmış önerdi Bir uygulamanın kullanım ömrü boyunca daha iyi kanıtlanması daha muhtemel bir yaklaşım olduğu için, arama yapmaktan çekinmeyin).


0

Bir miktar unordoks olan çöp imhası için bir kullanımım var.

Olarak bilinen çirkin, aksak, inelegant ve hata eğilimli deyim kullanarak nesne bertarafı uygulanması, ne yazık ki C # dünyasında çok yaygındır bu yanlış bir uygulama mevcuttur ıdisposable-bertaraf . MSDN , uzunluğunu açıklar ve pek çok kişi onunla yemin eder, dindar bir şekilde takip eder, saatlerce saatler geçirerek tam olarak nasıl yapılması gerektiğini tartışır .

(Burada çirkin dediğim şeyin nesne imha modelinin kendisi olmadığını , çirkin dediğim şeyin belirli bir IDisposable.Dispose( bool disposing )deyim olduğunu unutmayın.)

Bu deyim icat edildi, çünkü nesnelerin yıkıcısının, kaynakları temizlemek için her zaman çöp toplayıcı tarafından çağrılacağını garanti etmek imkansızdır, böylece insanlar kaynak temizliği yaparlar IDisposable.Dispose()ve unuturlarsa, bir kez daha denerler. yıkıcı içinde. Bilirsin, sadece durumda.

Ancak IDisposable.Dispose(), temizlemek için hem yönetilen hem de yönetilmeyen nesnelere sahip olabilirsiniz, ancak yönetilenler IDisposable.Dispose()yıkıcı içinden çağrıldığında temizlenemezler , çünkü o zamanlar çöp toplayıcı tarafından çoktan halledilmişlerdir. Bu , hem yönetilen hem de yönetilmeyen nesnelerin temizlenip temizlenmeyeceğini veya yalnızca yönetilmeyen olanları bilmek için Dispose()bir bool disposingbayrağı kabul eden ayrı bir yönteme duyulan gereksinimdir .

Affedersiniz, ama bu sadece delilik.

Einstein’ın aksiyomuna bakıyorum, ki bu mümkün olduğu kadar basit olmalı, fakat daha basit olmamalı. Açıkçası, kaynakların temizlenmesini ihmal edemiyoruz, bu nedenle mümkün olan en basit çözüm en azından bunu içermelidir. Bir sonraki en basit çözüm, yıkıcıya alternatif bir geri çekilme olarak güvenerek bir şeyleri karmaşıklaştırmaksızın, her şeyi imha edilmesi gereken tam zamanda elden çıkarmayı içerir.

Şimdi, açık konuşmak gerekirse, hiç programcı hiç çağırmak için unutma hata yapacak garanti etmek elbette mümkün değildir IDisposable.Dispose(), ancak ne olabilir yapmak bu hatayı yakalamak için yıkıcı kullanmaktır. Gerçekten çok basittir: disposedtek kullanımlık nesnenin bayrağının asla ayarlanmadığını tespit ederse, yıkıcıların yapması gereken tek şey bir günlük girişi oluşturmaktır true. Bu yüzden, yıkıcı kullanımı, elden çıkarma stratejimizin ayrılmaz bir parçası değil, kalite güvence mekanizmamızdır. Ve bu sadece bir hata ayıklama modu testi olduğundan, tüm yıkıcımızı bir #if DEBUGbloğun içine yerleştirebiliriz , bu yüzden bir üretim ortamında hiçbir zaman yıkım cezasına çarptırılmaz. ( IDisposable.Dispose( bool disposing )Deyim bunu reçete eder)GC.SuppressFinalize() Sonlandırma ek yükünü azaltmak için kesin olarak başlatılmalıdır, ancak mekanizmamla üretim ortamındaki bu ek yükü tamamen önlemek mümkündür.)

Sonsuz sert hataya karşılık yumuşak hata argümanına IDisposable.Dispose( bool disposing )indirgeyen şey : deyim yumuşak bir hata yaklaşımıdır ve programcının Dispose()mümkün olduğunda sistem arızası olmadan çalıştırmayı unutmasını sağlama girişimini temsil eder . Sert hata yaklaşımı, programcının her zaman çağrılacağından emin olması gerektiğini söyler Dispose(). Genelde çoğu zaman sert hata yaklaşımıyla verilen ceza, iddia hatasıdır, ancak bu özel durum için, bir istisna yaparız ve basit bir hata günlüğü girişinin basit bir şekilde verilmesi için cezayı azaltırız.

Bu nedenle, bu mekanizmanın çalışması için başvurumuzun DEBUG sürümü, tüm yıkıcıların çağrılacağını garanti etmek ve böylece atmayı IDisposableunuttuğumuz herhangi bir nesneyi yakalamak için, istifa etmeden önce tam bir çöp imhası gerçekleştirmelidir .


Now, strictly speaking, it is of course impossible to guarantee that no programmer will ever make the mistake of forgetting to invoke IDisposable.Dispose()Aslında, C # ile yetenekli olduğunu düşünmeme rağmen değil. Kaynak gösterme; bunun yerine, onunla yapacağınız her şeyi (temel olarak bir monad) tanımlamak için bir DSL, ayrıca kaynağı alan, işleri yapan, serbest bırakan ve sonucu veren bir DSL sağlayın. İşin püf noktası, eğer biri kaynağa bir referans kaçakçılığı yaparsa, çalıştırma işlevinin başka bir çağrısında kullanılamayacağını garanti etmek için type sistemini kullanmaktır.
Doval

2
Dispose(bool disposing)(Üzerinde tanımlanmayan) sorunu IDisposable, hem yönetilen hem de yönetilmeyen nesnelerin temizliği ile uğraşmak için kullanılmasıdır, nesnenin bir alanı olarak (veya başka türlü sorumludur) olması, yanlış sorunu çözmektedir. Endişelenecek başka atılabilir nesnesi olmayan yönetilen bir nesnede yönetilmeyen nesneler, o zaman tüm Dispose()yöntemler ya bunlardan biri olacaktır (sonlandırıcının gerektiğinde aynı temizliği yapmasını sağlayın) ya da yalnızca elden çıkarmak için yönetilen nesneler olacaktır (bir sonlandırıcının olmaması) hiç) ve bool disposingyok olma ihtiyacı ortadan kalkıyor
Jon Hanna

-1 kesinleşmenin gerçekte nasıl çalıştığından dolayı kötü tavsiyeler. dispose(disposing)Terribad olan deyim konusundaki amacınıza tamamen katılıyorum , ancak insanlar sadece yönetilen kaynaklara sahip olduklarında ( DbConnectionörneğin, nesne yönetiliyor , saptırılmıyor ya da anlaşılmıyor) ve bu nedenle SİZİN SADECE YAPMALIDIR. hİÇ yönetilmeyen, PINVOKED, COM sıralıyor VEYA GÜVENLİ OLMAYAN KODU İLE fINALIZER UYGULANMASI . Ben, pahalı finalizers ne kadar acı cevabım yukarıda ayrıntıları yok sen olmadıkça bunları kullanmak yönetilmeyen sınıfınızda kaynaklar.
Jimmy Hoffa,

2
Neredeyse sana +1 vermek istiyorum, çünkü sadece bir çok insanın dispose(dispoing)deyimde önemli ve önemli bir şey olarak aldıkları bir şeyi reddettiğin için , ama gerçek şu ki, bu kadar yaygın, çünkü insanlar GC gibi şeylerden çok korkuyorlar. Bu ( disposeGC ile yapmak zilch olmalıdır), sadece reçete edilen ilacı bile araştırmadan almak için onları hak eder. Teftişiniz için iyi, ama en büyük olanı kaçırdınız (finalizatörleri olması gerektiğinden daha fazla teşvik ediyor)
Jimmy Hoffa

1
@JimmyHoffa girişiniz için teşekkür ederiz. Bir kesinleştiricinin normalde yalnızca yönetilmeyen kaynakları serbest bırakmak için kullanılması gerektiğine katılıyorum , ancak DEBUG inşasında bu kuralın uygulanabilir olmadığı ve DEBUG inşasında sonlandırıcıları böcekleri yakalamak için kullanmamızın serbest olması gerektiği konusunda hemfikir değil misiniz? Burada önerdiğim tek şey bu, bu yüzden neden onunla konuştuğunuzu anlamıyorum. Bu yaklaşıma dünyanın java tarafında daha uzun bir açıklaması için ayrıca bkz. Programmers.stackexchange.com/questions/288715/… .
Mike Nakis

0

Ne tür bir senaryoda çöp toplanmasının zorlanmasının iyi ya da makul bir fikir olduğunu söyleyebilir misiniz? C # özel durumlar istemiyorum, bunun yerine, çöp toplayıcı olan tüm programlama dilleri. GC'yi Java gibi tüm dillerde zorlayamayacağınızı biliyorum, ancak yapabileceğinizi varsayalım.

Sadece teorik olarak konuşursak ve bazı GC uygulamaları gibi sorunları toplama çevrimlerinde yavaşlatan bazı şeyleri göz ardı ederek, çöp toplamaya zorlamak için aklıma gelen en büyük senaryo, örneğin çarpma nedeniyle işaretçi çarpmalarını sallamak için mantıklı sızıntıların tercih edileceği, kritik bir yazılımdır. beklenmedik zamanlarda, insan yaşamına ya da bu tür bir şeye mal olabilir.

Flash oyunları gibi GC dillerini kullanarak yazılan bazı indie oyunlara bakarsanız deli gibi sızarlar, ancak çarpmazlar. Oyunun 20 dakikası, oyunu oynamak için 20 dakika hafızayı alabilir çünkü oyunun kod tabanının bir kısmı listeyi boşa çıkarmak veya listeden çıkarmak için bir referans ayarlamayı unutmuş ve kare oranları acı çekmeye başlamış olabilir, ancak oyun hala çalışıyor. Ayakkabılı C veya C ++ kodları kullanılarak yazılmış benzer bir oyun, aynı tür kaynak yönetimi hatası nedeniyle sarkan işaretçilere erişmenin bir sonucu olarak çökebilir, ancak çok fazla sızıntı olmaz.

Oyunlar için, hızlı bir şekilde tespit edilip düzeltilebilmesi anlamında kaza tercih edilebilir, ancak kritik bir program için beklenmedik zamanlarda kaza yapmak birini öldürebilir. Bu yüzden, ana durumlar, çökme ya da diğer bazı formların güvenliğin kesinlikle kritik olduğu senaryolar olacağını düşünüyorum ve mantıksal bir sızıntı, kıyaslandığında nispeten önemsiz bir şeydir.

GC'yi zorlamanın kötü olduğunu düşündüğüm ana senaryo, mantıksal sızıntının gerçekte kazadan daha az tercih edilen şeyler olduğu içindir. Örneğin, oyunlarda, kazayla bir kimsenin öldürülmesi gerekmeyebilir ve dahili testler sırasında kolayca yakalanabilir ve düzeltilebilir; oysa, birkaç dakika içinde oyunu oynanamayacak kadar ağır kılmadığı sürece, ürün gönderildikten sonra bile mantıksal bir sızıntı fark edilmeyebilir . Bazı alanlarda, test sırasında meydana gelen kolayca çoğaltılabilen bir çökme, bazen kimsenin hemen fark etmediği bir sızıntıya tercih edilir.

Bir ekibi GC'yi zorlamanın neresi tercih edebileceğini düşünebildiğim bir başka durum da, sadece bir görevi yapan ve sonra kapanan komut satırından yürütülen bir şey gibi, çok kısa ömürlü bir programdır. Bu durumda, programın ömrü herhangi bir mantıksal sızıntıyı önemsiz yapmak için çok kısadır. Büyük kaynaklar için bile, mantıksal sızıntılar, genellikle yazılımı çalıştırdıktan yalnızca saatler veya dakikalar sonra sorunlu hale gelir; bu nedenle, yalnızca 3 saniye boyunca yürütülmesi planlanan bir yazılımın, mantıksal sızıntılar ile ilgili sorun yaşaması olası değildir ve bu, çok fazla şey yapabilir. Ekip sadece GC kullandıysa, kısa ömürlü programlar yazmak daha kolay.

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.