.NET çöp toplayıcısı (GC), nesneleri küçük ve büyük nesnelere böler. Bir nesne büyük olduğunda, bazı öznitelikleri nesnenin küçük olduğundan daha önemli hale gelir. Örneğin sıkıştırmak( başka bir deyişle yığının başka bir yerine bellekte kopyalamak) pahalıya patlayabilir. Bu nedenle, çöp toplayıcı büyük nesne yığınına (LOH) büyük nesneler yerleştirir. Bu makalede, bir nesneyi büyük bir nesne olarak nitelemeyi, büyük nesnelerin nasıl toplandığını ve büyük nesnelerin ne tür performans etkilerine neden olduğu açıklanır.
Önemli
Bu makalede, yalnızca Windows sistemlerinde çalışan .NET Framework ve .NET Core'daki büyük nesne yığını ele alınmaktadır. Diğer platformlarda .NET uygulamalarında çalışan LOH'yi kapsamaz.
Bir nesnenin LOH'ta nasıl sona ermesi
Nesne boyutu 85.000 bayttan büyük veya buna eşitse, büyük bir nesne olarak kabul edilir. Bu sayı, performans ayarlaması tarafından belirlendi. Bir nesne ayırma isteği 85.000 veya daha fazla bayt için olduğunda, çalışma zamanı bunu büyük nesne yığınına ayırır.
Bunun ne anlama geldiğini anlamak için, çöp toplayıcı hakkında bazı temelleri incelemek yararlı olur.
Çöp toplayıcı bir nesil toplayıcıdır. Üç nesil vardır: 0. nesil, 1. nesil ve 2. nesil. Üç nesil olmasının nedeni, iyi ayarlanmış bir uygulamada çoğu nesnenin 0. nesilde ölmesidir. Örneğin, bir sunucu uygulamasında, istek tamamlandıktan sonra her istekle ilişkili ayırmaların ölmesi gerekir. Uçuş içi ayırma istekleri onu 1. nesile dönüştürecek ve orada ölecek. Temelde, 1. nesil, genç nesne alanları ve uzun ömürlü nesne alanları arasında bir arabellek işlevi görür.
Yeni ayrılan nesneler yeni bir nesne nesli oluşturur ve örtük olarak 0. nesil koleksiyonlardır. Ancak, büyük nesnelerse, bazen 3. nesil olarak adlandırılan büyük nesne yığınına (LOH) gider. 3. nesil, mantıksal olarak 2. neslin bir parçası olarak toplanan fiziksel bir nesildir.
Büyük nesneler yalnızca 2. nesil bir koleksiyon sırasında toplandığından 2. nesil nesnelere aittir. Bir nesil toplandığında, tüm genç nesilleri de toplanır. Örneğin, 1. nesil GC gerçekleştiğinde hem 1. nesil hem de 0 toplanır. 2. nesil GC gerçekleştiğinde de yığının tamamı toplanır. Bu nedenle, 2. nesil GC tam GC olarak da adlandırılır. Bu makale, tam GC yerine 2. nesil GC'yi ifade eder, ancak terimler birbirinin yerine değiştirilebilir.
Nesiller GC yığınının mantıksal bir görünümünü sağlar. Fiziksel olarak, nesneler yönetilen yığın segmentlerinde yaşar. Yönetilen yığın kesimi, yönetilen kod adına VirtualAlloc işlevini çağırarak GC'nin işletim sisteminden ayırdığı bir bellek öbeğidir. CLR yüklendiğinde GC iki ilk yığın kesimi ayırır: biri küçük nesneler (küçük nesne yığını veya SOH) ve biri büyük nesneler (büyük nesne yığını) için.
Daha sonra bu yönetilen yığın kesimlerine yönetilen nesneler yerleştirilerek ayırma istekleri karşılanıyor. Nesne 85.000 bayttan küçükse, SOH için kesime konur; aksi takdirde, bir LOH segmenti üzerine konur. Segmentler, üzerine daha fazla nesne ayrıldığı için işlenir (daha küçük öbekler halinde).
SOH için, gc'den kurtulan nesneler yeni nesillere yükseltilir. 0. nesil bir koleksiyondan kurtulan nesneler artık 1. nesil nesneler olarak kabul edilir ve bu şekilde devam eder. Ancak, en eski nesilden hayatta kalan nesneler hala en eski nesilde olarak kabul edilir. Başka bir deyişle, 2. nesilden sağ kalanlar 2. nesil nesnelerdir; ve LOH'dan kurtulanlar LOH nesneleridir (gen2 ile toplanır).
Kullanıcı kodu yalnızca 0. kuşakta (küçük nesneler) veya LOH'da (büyük nesneler) ayırabilir. Yalnızca GC, 1. nesildeki (0. nesilden kurtulanları tanıtarak) ve 2. nesildeki (1. nesilden kalanları tanıtarak) nesneleri "ayırabilir".
Bir çöp toplama tetiklendiğinde GC canlı nesneler üzerinden izler ve bunları sıkıştırr. Ancak sıkıştırma pahalı olduğundan GC , LOH'yi süpürür ; daha sonra büyük nesne ayırma isteklerini karşılamak için yeniden kullanılabilecek boş nesneler listesi oluşturur. Bitişik ölü nesneler tek bir boş nesneye dönüştürülür.
.NET Core ve .NET Framework (.NET Framework 4.5.1 ile başlayarak), kullanıcıların loh'un bir sonraki tam engelleme GC'si sırasında sıkıştırılacağını belirtmesine olanak tanıyan özelliği içerir GCSettings.LargeObjectHeapCompactionMode . Gelecekte .NET, LOH'yi otomatik olarak sıkıştırmaya karar verebilir. Başka bir deyişle, büyük nesneleri ayırır ve taşınmadığından emin olmak isterseniz, yine de bunları sabitlemeniz gerekir.
Şekil 1'de, GC'nin ilk nesil 0 GC'nin ardından 1. nesil oluşturması ve Obj3 ölü olması Obj1 ve ilk nesil 1 GC'nin ardından Obj2Obj5 2. nesil oluşturması senaryo gösterilmektedir. Bunun ve aşağıdaki rakamların yalnızca çizim amaçlı olduğunu unutmayın; yığında ne olduğunu daha iyi göstermek için çok az nesne içerir. Gerçekte, bir GC'ye genellikle daha fazla nesne dahil edilir.
Şekil 1: 0. nesil ve 1. nesil GC.
Şekil 2'de, bunu gören Obj1 ve Obj2 ölen 2. nesil bir GC'nin ardından, GC'nin ve tarafından Obj1 kullanılan ve Obj2için ayırma isteğini karşılamak için Obj4kullanılan bitişik boş bellek alanı oluşturduğu gösterilmiştir. Ayırma isteklerini karşılamak için, segmentin sonuna kadar olan son nesneden Obj3sonraki alan da kullanılabilir.
Şekil 2: 2. nesil GC'nin ardından
Büyük nesne ayırma isteklerini karşılamak için yeterli boş alan yoksa GC ilk olarak işletim sisteminden daha fazla kesim almayı dener. Bu başarısız olursa, biraz alan boşaltma umuduyla 2. nesil GC'yi tetikler.
1. nesil veya 2. nesil GC sırasında çöp toplayıcı, üzerinde canlı nesne olmayan kesimleri VirtualFree işlevini çağırarak işletim sistemine geri gönderir. Segmentin sonuna kadar olan son canlı nesneden sonraki alan ayrıştırılır (0/1. nesil'in canlı olduğu kısa ömürlü segment dışında, çöp toplayıcının bir kısmını işlemesini sağlar çünkü uygulamanız hemen ayıracaktır). Boş alanlar sıfırlandıklarına rağmen işlenmeye devam eder. Bu da işletim sisteminin diske geri veri yazması gerekmemesi anlamına gelir.
LOH yalnızca 2. nesil GC'ler sırasında toplandığından, LOH segmenti yalnızca böyle bir GC sırasında serbest bırakılır. Şekil 3'te, çöp toplayıcının bir segmenti (segment 2) işletim sistemine geri bıraktığı ve kalan segmentlerde daha fazla alan bıraktığı bir senaryo gösterilmektedir. Büyük nesne ayırma isteklerini karşılamak için kesimin sonundaki ayrıştırma alanını kullanması gerekiyorsa, belleği yeniden işler. (commit/decommit açıklaması için VirtualAlloc.)
Şekil 3: 2. nesil GC'nin ardından gelen LOH
Büyük bir nesne ne zaman toplanır?
Genel olarak, gc aşağıdaki üç koşuldan biri altında gerçekleşir:
Ayırma, 0. nesil veya büyük nesne eşiğini aşıyor.
Eşik, bir neslin özelliğidir. Atık toplayıcı içine nesne ayırdığında bir nesil için bir eşik ayarlanır. Eşik aşıldığında, bu nesilde bir GC tetikler. Küçük veya büyük nesneleri ayırdığınızda, sırasıyla 0. nesil ve LOH eşiklerini kullanırsınız. Atık toplayıcı 1. ve 2. nesillere ayrıldığında eşiklerini tüketir. Bu eşikler, program çalışırken dinamik olarak ayarlanmıştır.
Bu tipik bir durumdur; çoğu GC yönetilen yığındaki ayırmalar nedeniyle oluşur.
Parametresiz GC.Collect() yöntem çağrılırsa veya bağımsız değişken olarak başka bir aşırı yükleme geçirilirse GC.MaxGeneration , LOH yönetilen yığının geri kalanıyla birlikte toplanır.
Sistem düşük bellek durumunda.
Bu durum, atık toplayıcı işletim sisteminden yüksek bellek bildirimi aldığında oluşur. Çöp toplayıcı, 2. nesil GC yapmanın üretken olacağını düşünüyorsa, bir tane tetikler.
LOH performansının etkileri
Büyük nesne yığınındaki ayırmalar performansı aşağıdaki yollarla etkiler.
Ayırma maliyeti.
CLR, verdiği her yeni nesne için belleğin temizlendiğini garanti eder. Bu, büyük bir nesnenin ayırma maliyetinin bellek temizleme (GC tetiklemediği sürece) tarafından baskın olduğu anlamına gelir. Bir bayt temizlemek için iki döngü gerekiyorsa, en küçük büyük nesneyi temizlemek için 170.000 döngü gerekir. 2 GHz makinedeki 16 MB'lık nesnenin belleğinin temizlenmesi yaklaşık 16 ms sürer. Bu oldukça büyük bir maliyet.
Koleksiyon maliyeti.
LOH ve 2. nesil birlikte toplandığından, birinin eşiği aşılırsa 2. nesil bir koleksiyon tetiklenir. 2. nesil bir koleksiyon LOH nedeniyle tetikleniyorsa, 2. nesil GC'nin ardından çok daha küçük olmayabilir. 2. nesilde çok fazla veri yoksa bunun çok az etkisi vardır. Ancak 2. nesil büyükse, çok sayıda 2. nesil GC tetiklendiğinde performans sorunlarına neden olabilir. Birçok büyük nesne geçici olarak ayrılmışsa ve büyük bir SOH'niz varsa, GC'leri yaparken çok fazla zaman harcıyor olabilirsiniz. Buna ek olarak, gerçekten büyük nesneleri ayırmaya ve bırakmaya devam ederseniz ayırma maliyeti gerçekten eklenebilir.
Başvuru türlerine sahip dizi öğeleri.
LOH üzerindeki çok büyük nesneler genellikle dizilerdir (gerçekten büyük bir örnek nesnesi olması çok nadirdir). Bir dizinin öğeleri başvuru bakımından zenginse, öğeler başvuru açısından zengin değilse mevcut olmayan bir maliyete neden olur. öğesi herhangi bir başvuru içermiyorsa, çöp toplayıcının diziyi hiç geçirmesi gerekmez. Örneğin, düğümleri bir ikili ağaçta depolamak için bir dizi kullanırsanız, bunu uygulamanın bir yolu, bir düğümün sağ ve sol düğümüne gerçek düğümler tarafından başvurmaktır:
C#
classNode
{
Data d;
Node left;
Node right;
};
Node[] binary_tr = new Node [num_nodes];
Büyükse num_nodes , atık toplayıcının öğe başına en az iki başvurudan geçmesi gerekir. Alternatif bir yaklaşım, sağ ve sol düğümlerin dizinini depolamaktır:
C#
classNode
{
Data d;
uint left_index;
uint right_index;
} ;
Sol düğümün verilerine olarak left.dbaşvurmak yerine olarak başvurursunuz binary_tr[left_index].d. Ayrıca atık toplayıcının sol ve sağ düğüm için başvurulara bakması gerekmez.
Üç faktörden ilk ikisi genellikle üçüncü faktörden daha önemlidir. Bu nedenle, geçici nesneler ayırmak yerine yeniden kullandığınız büyük nesnelerden oluşan bir havuz ayırmanızı öneririz.
LOH için performans verilerini toplama
Belirli bir alan için performans verilerini toplamadan önce, aşağıdakileri zaten yapmış olmanız gerekir:
Bu alana bakman gerektiğine dair kanıt buldum.
Gördüğünüz performans sorununu açıklayacak hiçbir şey bulamadan bildiğiniz diğer alanları tükettiniz.
.NET CLR Bellek performans sayaçları genellikle performans sorunlarını araştırmada iyi bir ilk adımdır (ETW olaylarını kullanmanızı öneririz). Performans sayaçlarına bakmanın yaygın bir yolu Performans İzleyicisi (perfmon.exe) kullanmaktır. İlgilendiğiniz işlemlere yönelik ilginç sayaçları eklemek için Ekle (Ctrl + A) öğesini seçin. Performans sayacı verilerini bir günlük dosyasına kaydedebilirsiniz.
.NET CLR Bellek kategorisindeki aşağıdaki iki sayaç LOH için geçerlidir:
# 2. Nesil Koleksiyonlar
İşlem başladıktan sonra 2. nesil GC'lerin oluşma sayısını görüntüler. Sayaç, 2. nesil koleksiyonun sonunda artırılır (tam çöp toplama olarak da adlandırılır). Bu sayaç, gözlemlenen son değeri görüntüler.
Büyük Nesne Yığını boyutu
LOH'nin boş alan da dahil olmak üzere geçerli boyutunu bayt cinsinden görüntüler. Bu sayaç, her ayırmada değil, çöp toplamanın sonunda güncelleştirilir.
Ayrıca sınıfını kullanarak PerformanceCounter program aracılığıyla performans sayaçlarını sorgulayabilirsiniz. LOH için olarak ".NET CLR Bellek" ve olarak CategoryName "Büyük Nesne Yığını boyutu" CounterNamebelirtin.
Rutin test sürecinin bir parçası olarak sayaçları program aracılığıyla toplamak yaygın bir durumdur. Normal olmayan değerlerle sayaçları tespit ettiğinizde, araştırmaya yardımcı olmak için daha ayrıntılı veriler almak için diğer araçları kullanın.
Not
ETW çok daha zengin bilgiler sağladığından performans sayaçları yerine ETW olaylarını kullanmanızı öneririz.
ETW olayları
Çöp toplayıcı, yığının ne yaptığını ve nedenini anlamanıza yardımcı olmak için zengin bir ETW olayları kümesi sağlar. Aşağıdaki blog gönderileri, ETW ile GC olaylarını toplamayı ve anlamayı gösterir:
Geçici LOH ayırmalarının neden olduğu aşırı 2. nesil GC'leri tanımlamak için, GC'lerin Tetikleyici Nedeni sütununa bakın. Yalnızca geçici büyük nesneleri ayıran basit bir test için, aşağıdaki PerfView komutuyla ETW olayları hakkında bilgi toplayabilirsiniz:
Gördüğünüz gibi, tüm GC'ler 2. nesil GC'lerdir ve tümü AllocLarge tarafından tetiklenir. Bu da büyük bir nesne ayırmanın bu GC'yi tetiklediği anlamına gelir. LOH Hayatta Kalma Oranı % sütununda %1 yazan bu ayırmaların geçici olduğunu biliyoruz.
Bu büyük nesneleri kimin ayırdığını size söyleyen ek ETW olayları toplayabilirsiniz. Aşağıdaki komut satırı:
Console
perfview /GCOnly /AcceptEULA /nogui collect
yaklaşık her 100 bin ayırmada tetiklenen bir AllocationTick olayı toplar. Başka bir deyişle, büyük bir nesne her ayrıldığında bir olay tetiklenir. Daha sonra, büyük nesneleri ayıran çağrı yığınlarını gösteren GC Heap Alloc görünümlerinden birine bakabilirsiniz:
Gördüğünüz gibi, bu yalnızca yönteminden büyük nesneleri ayıran çok basit bir testtir Main .
Hata ayıklayıcısı
Sahip olduğunuz tek şey bir bellek dökümüyse ve LOH'da gerçekte hangi nesnelerin olduğuna bakmanız gerekiyorsa, .NET tarafından sağlanan SoS hata ayıklayıcısı uzantısını kullanabilirsiniz.
Not
Bu bölümde belirtilen hata ayıklama komutları Windows hata ayıklayıcıları için geçerlidir.
Aşağıda LOH analizinden elde edilen örnek çıkış gösterilmektedir:
Console
0:003> .loadby sos mscorwks
0:003> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x013e35ec
sdgeneration 1 starts at 0x013e1b6c
generation 2 starts at 0x013e1000
ephemeral segment allocation context: none
segment begin allocated size
0018f2d0 790d5588 790f4b38 0x0001f5b0(128432)
013e0000 013e1000 013e35f8 0x000025f8(9720)
Large object heap starts at 0x023e1000
segment begin allocated size
023e0000 023e1000 033db630 0x00ffa630(16754224)
033e0000 033e1000 043cdf98 0x00fecf98(16699288)
043e0000 043e1000 05368b58 0x00f87b58(16284504)
Total Size 0x2f90cc8(49876168)
------------------------------
GC Heap Size 0x2f90cc8(49876168)
0:003> !dumpheap -stat 023e1000 033db630
total 133 objects
Statistics:
MT Count TotalSize Class Name
001521d0 66 2081792 Free
7912273c 63 6663696 System.Byte[]
7912254c 4 8008736 System.Object[]
Total 133 objects
LOH yığın boyutu (16.754.224 + 16.699.288 + 16.284.504) = 49.738.016 bayttır. 023e1000 ve 033db630 adresleri arasında, 8.008.736 bayt bir nesne dizisi System.Object tarafından işgal edilir, 6.663.696 bayt bir nesne dizisi System.Byte tarafından kaplanmıştır ve 2.081.792 bayt boş alan tarafından işgal edilir.
Bazen hata ayıklayıcı, LOH'nin toplam boyutunun 85.000 bayttan az olduğunu gösterir. Bunun nedeni, çalışma zamanının büyük bir nesneden daha küçük olan bazı nesneleri ayırmak için LOH'yi kullanmasıdır.
LOH sıkıştırılmadığından, loh bazen parçalanma kaynağı olarak düşünülüyor. Parçalanma şu anlama gelir:
Yönetilen nesneler arasındaki boş alan miktarıyla gösterilen yönetilen yığının parçalanması. SoS'da !dumpheap –type Free komut, yönetilen nesneler arasındaki boş alan miktarını görüntüler.
olarak MEM_FREEişaretlenmiş bellek olan sanal bellek (VM) adres alanının parçalanması. Windbg'de çeşitli hata ayıklayıcı komutlarını kullanarak alabilirsiniz.
Aşağıdaki örnekte VM alanında parçalanma gösterilmektedir:
Atık toplayıcının sık sık işletim sisteminden yeni yönetilen yığın kesimleri almasını ve boş olanları işletim sistemine geri bırakmasını gerektiren geçici büyük nesnelerin neden olduğu VM parçalanmalarını görmek daha yaygındır.
LOH'un VM parçalanmalarına neden olup olmadığını doğrulamak için VirtualAlloc ve VirtualFree'de bir kesme noktası ayarlayarak bunları kimin çağırdığını görebilirsiniz. Örneğin, işletim sisteminden 8 MB'tan büyük sanal bellek öbeklerini ayırmaya çalışan kişileri görmek için aşağıdaki gibi bir kesme noktası ayarlayabilirsiniz:
Console
bp kernel32!virtualalloc "j (dwo(@esp+8)>800000) 'kb';'g'"
CLR 2.0, segmentlerin (büyük ve küçük nesne yığınları dahil) sık sık alındığı ve yayımlandığı senaryolar için yararlı olabilecek VM Biriktiricisi adlı bir özellik ekledi. VM Hoarding'i belirtmek için, barındırma API'si aracılığıyla çağrılan STARTUP_HOARD_GC_VM bir başlangıç bayrağı belirtirsiniz. CLR, boş kesimleri işletim sistemine geri göndermek yerine bu kesimlerdeki belleğin kullanımdan kalkmasını sağlar ve bunları bekleme listesine alır. (CLR'nin bunu çok büyük segmentler için yapmadığını unutmayın.) CLR daha sonra yeni segment isteklerini karşılamak için bu kesimleri kullanır. Uygulamanızın bir sonraki yeni segmente ihtiyacı olduğunda CLR, yeterince büyük bir segment bulabiliyorsa bu bekleme listesindeki bir segmenti kullanır.
VM istifleme, bellek yetersiz özel durumlarını önlemek için sistemde çalışan baskın uygulamalar olan bazı sunucu uygulamaları gibi zaten edindikleri kesimleri tutmak isteyen uygulamalar için de yararlıdır.
Uygulamanızın oldukça kararlı bellek kullanımına sahip olduğundan emin olmak için bu özelliği kullanırken uygulamanızı dikkatle test etmenizi kesinlikle öneririz.
GitHub'da bizimle işbirliği yapın
Bu içeriğin kaynağı GitHub'da bulunabilir; burada ayrıca sorunları ve çekme isteklerini oluşturup gözden geçirebilirsiniz. Daha fazla bilgi için katkıda bulunan kılavuzumuzu inceleyin.
.NET geri bildirimi
.NET, açık kaynak bir projedir. Geri bildirim sağlamak için bir bağlantı seçin:
Diğer geliştiriciler ve uzmanlarla gerçek dünyadaki kullanım örneklerini temel alan ölçeklenebilir yapay zeka çözümleri oluşturmak için toplantı serisine katılın.
Azure HPC, en iyi uygulama performansı, ölçeklenebilirlik ve değer sunmak için önde gelen işlemcileri ve HPC sınıfı InfiniBand ara bağlantısını kullanan HPC ve AI iş yükü için amaca yönelik bir bulut özelliğidir. Azure HPC, iş ve teknik gereksinimleriniz değiştikçe dinamik olarak ayrılabilen yüksek oranda kullanılabilir hpc ve yapay zeka teknolojileri aracılığıyla kullanıcıların yenilik, üretkenlik ve iş çevikliğini ortaya çıkarmalarını sağlar. Bu öğrenme yolu, Azure HPC'yi kullanmaya başlamanıza yardımcı o
Çöp toplama ve bellek kullanımıyla ilgili sorunlar hakkında bilgi edinin. Atık toplamanın uygulamalarınız üzerindeki etkisini en aza indirmeyi öğrenin.