Neden Çöp Toplama sadece yığını temizliyor?


28

Temel olarak, şu ana kadar çöp toplanmasının şu anda işaret edilmeyen herhangi bir veri yapısını sonsuza kadar sildiğini öğrendim. Ancak bu sadece öbekleri böyle durumlar için kontrol eder.

Neden veri bölümünü (küresel, sabit vb.) Veya yığını da kontrol etmiyor? Çöp toplamak istediğimiz tek şey, yığınla ilgili olan nedir?


21
"yığın süpürme" "yığını istif" whack daha güvenlidir ... :-)
Brian Knoblauch

Yanıtlar:


62

Çöp toplayıcı yapar yığın şeyler şu anda yığın şeyler tarafından (için sivri) kullanılmaktadır görmek için - yığını tarayın.

Çöp toplayıcının yığın belleği toplamayı düşünmemesi bir anlam ifade etmiyor çünkü yığın bu şekilde yönetilemiyor: Yığın üzerindeki her şeyin "kullanımda" olduğu kabul edilir. Yığın tarafından kullanılan bellek, yöntem çağrılarından döndüğünüzde otomatik olarak geri kazanılır. Yığın alanın bellek yönetimi o kadar basit, ucuz ve kolaydır ki, çöp toplama işleminin dahil olmasını istemezsiniz.

(Yığın çerçevelerinin yığın içinde depolanan birinci sınıf nesneler olduğu ve diğer tüm nesneler gibi toplanan çöplerin olduğu smalltalk gibi sistemler vardır. Ancak günümüzde popüler yaklaşım bu değildir. Java'nın JVM'si ve Microsoft'un CLR'si donanım yığınını ve bitişik belleği kullanır. .)


7
+1 yığını her zaman tam olarak erişilebilir durumdadır, bu yüzden süpürmek için hiçbir anlamı yoktur
cırcır ucubesi

2
+1 teşekkür ederim, doğru cevabı vurmak için 4 mesaj aldı. Eğer yığın şeyi kullanımda olduğu "kabul" olduğunu söylemek zorunda neden o bilmiyorum olduğu kullanımda olan yığın halen kullanılmakta nesneleri gibi en azından güçlü bir anlamda olarak kullanımda - ama bu gerçek bir nitpick var çok iyi bir cevap.
psr

@psr, yığındaki her şeyin kolayca erişilebilir olduğu ve yöntem geri dönene kadar toplanmasına gerek olmadığı, ancak (RAII) zaten açıkça yönetildiği anlamına gelir
cırcır ucube

@ ratchetfreak - biliyorum. Ve sadece "kabul edildi" kelimesinin muhtemelen gerekmediğini kastetmiştim, onsuz daha güçlü bir ifade vermemiz sorun değil.
psr

5
@ psr: Ben katılmıyorum. " kullanımda olduğu düşünülmektedir " hem yığın hem de yığın için çok önemli nedenlerden dolayı daha doğrudur. İstediğin, bir daha kullanılmayacak olanları atmak; Yaptığınız şey ulaşılamaz olanı atmanız . Hiç ihtiyacınız olmayacak ulaşılabilir verilere sahip olabilirsiniz; Bu veriler büyüdüğünde, bir bellek sızıntınız var (evet, birçok kişinin düşündüğünün aksine, GC’lerin dillerinde bile mümkündür). Ve istif sızıntısının da olabileceği öne sürülebilir; en yaygın örnek, kuyruk yinelemeli programlardaki gereksiz yığın çerçevelerdir (örneğin JVM'de), kuyruk çağrısı ortadan kaldırılmadan çalışır.
Blaisorblade

19

Sorunuzu ters çevirin. Asıl motive edici soru hangi koşullar altında çöp toplama maliyetlerini önleyebiliriz?

Öncelikle kapalı ne olduğu çöp toplama maliyetleri? İki ana maliyet var. İlk önce neyin canlı olduğunu belirlemelisiniz ; Bu potansiyel olarak çok fazla çalışma gerektirir. İkinci olarak, halen hayatta olan iki şey arasında tahsis edilmiş bir şeyi serbest bıraktığınızda oluşan delikleri sıkıştırmanız gerekir . Bu delikler boşuna. Ancak onları sıkıştırmak da pahalıdır.

Bu maliyetleri nasıl önleyebiliriz?

Açıkça, hiç bir zaman uzun ömürlü bir şey tahsis etmediğiniz , daha sonra kısa ömürlü bir şey tahsis etmediğiniz , ardından uzun ömürlü bir şey tahsis etmediğiniz bir depolama kullanım şekli bulabilirseniz , deliklerin maliyetini ortadan kaldırabilirsiniz. Depolama alanınızın bazı alt kümeleri için, ardından gelen her tahsisatın o depoda bir öncekinden daha kısa ömürlü olduğunu garanti ederseniz, o zaman bu depoda hiçbir zaman delik açılmaz.

Ancak, delik problemini çözdüysek, çöp toplama problemini de çözdük . Depoda hala canlı olan bir şeyin var mı? Evet. Daha uzun ömürlü olmadan önce her şey tahsis edildi mi? Evet - bu varsayım, delik olasılığını nasıl ortadan kaldırdığımızdır. Bu nedenle yapmanız gereken tek şey "canlı olan en son tahsisat mı?" Demek. ve biliyorsun ki her şey o depoda canlıdır.

Sonraki tahsisatların önceki tahsisattan daha kısa ömürlü olduğunu bildiğimiz bir dizi depolama tahsisatı var mı? Evet! Aktivasyon kareleri, yaratıldıkları sırayla yıkılır, çünkü yarattıkları aktivasyondan daima daha kısa ömürlüdürler.

Bu nedenle aktivasyon çerçevelerini yığında depolayabilir ve asla toplanmayacaklarını biliriz. Yığında herhangi bir çerçeve varsa, altındaki tüm çerçeve kümesi daha uzun ömürlüdür, bu nedenle toplanmaları gerekmez. Ve onlar yaratıldıkları sırayla imha edilecekler. Böylece çöp toplama maliyeti, aktivasyon çerçeveleri için elimine edilir.

Bu yüzden ilk önce yığında geçici havuzumuz var: çünkü bir hafıza yönetimi cezası vermeden yöntem aktivasyonunu uygulamanın kolay bir yoludur.

(Tabii ki, aktivasyon çerçevelerinde referanslar tarafından belirtilen hafızanın toplanmasının maliyeti de hala oradadır.)

Şimdi, aktivasyon çerçevelerinin öngörülebilir bir sırada imha edilmediği bir kontrol akış sistemi düşünün . Kısa ömürlü bir aktivasyon uzun ömürlü bir aktivasyona neden olabilirse ne olur? Tahmin edebileceğiniz gibi, bu dünyada artık aktivasyon toplama ihtiyacını ortadan kaldırmak için yığını kullanamazsınız. Aktivasyon seti tekrar delikler içerebilir.

C # 2.0 şeklinde bu özelliği vardır yield return. Bir verim geri dönüşü yapan bir yöntem daha sonra - MoveNext'in bir sonraki çağrısında - ve bu olduğunda ne zaman olacağı tahmin edilemeyecek şekilde yeniden etkinleştirilecektir. Bu nedenle normal olarak yineleyici bloğunun aktivasyon çerçevesi için yığınta olacak olan bilgi, bunun yerine, numaralandırıcı toplandığında toplandığı çöpün bulunduğu yığınta depolanır.

Benzer şekilde, C # ve VB'nin sonraki sürümlerinde yer alan "async / await" özelliği, etkinliğin yöntemi "iyi" ve "devam ettiren" yöntemlerin eylemi sırasında iyi tanımlanmış noktalarda yöntemler oluşturmanıza izin verecektir. Aktivasyon çerçeveleri artık öngörülebilir bir şekilde yaratılmadığından ve imha edilmediğinden, yığında depolanan tüm bilgilerin yığın halinde depolanması gerekecektir.

Sadece on yıllardır kesin olarak düzenlenmiş bir biçimde oluşturulan ve tahrip olmuş aktivasyon çerçevelerine sahip dillerin modaya uygun olduğuna karar vermemiz bir kazadır. Modern diller giderek artan bir şekilde bu özellikten yoksun olduğundan, devam edenleri yığından ziyade çöp toplanan yığına yeniden yönlendiren daha fazla dil görmeyi bekliyoruz.


13

En açık cevap, belki de en dolu olanı, yığının örnek verilerinin konumu olmasıdır. Örnek verilere göre, çalışma zamanında yaratılan sınıfların, aka nesnelerin örneklerini temsil eden verileri kastediyoruz. Bu veriler doğal olarak dinamiktir ve bu nesnelerin sayısı ve bu nedenle aldıkları bellek miktarı yalnızca çalışma zamanında bilinmektedir. Bu hafızada bir miktar iyileşme olması gerekiyor, aksi takdirde uzun süredir çalışan programlar zamanla tüm hafızayı tüketir.

Sınıf tanımları, sabitler ve diğer statik veri yapıları tarafından tüketilen hafızanın doğal olarak kontrol edilmemiş olması muhtemeldir. Bu sınıfın bilinmeyen sayıda çalışma zamanı örneği başına bellekte yalnızca tek bir sınıf tanımı bulunduğundan, bu tür bir yapının bellek kullanımı için bir tehdit olmadığı anlamına gelir.


5
Ancak yığın “örnek veriler” in konumu değildir. Onlar da yığında olabilir.
svick

@svick Elbette dile bağlı olarak. Java yalnızca yığınla ayrılmış nesneleri destekler ve Vala yığınla ayrılmış (sınıf) ve yığınla ayrılmış (yapı) arasında açıkça ayrım yapar.
kabarık

1
@fluffy: bunlar çok sınırlı dillerdir, hiçbir dil belirtilmediğinden bunun genel olarak geçerli olduğunu varsayamazsınız.
Matthieu M.

@MatthieuM. Bu benim amacımdı.
kabarık

@fluffy: Öyleyse neden yığınlar yığında tahsis edilirken sınıflar yığında tahsis edilir?
Karanlık Tapınak

10

Çöp toplama işlemimizin nedenini akılda tutmaya değer: çünkü hafızayı ne zaman ayıracağınızı bilmek bazen zordur. Gerçekten sadece yığınla ilgili bir problemin var. Yığına tahsis edilen veriler sonunda ayrılacak, bu yüzden orada çöp toplama işlemi yapmanıza gerek yok. Veri bölümündeki şeylerin genellikle programın ömrü boyunca tahsis edildiği varsayılmaktadır.


1
Sadece 'nihayetinde' serbest bırakılmayacak, aynı zamanda doğru zamanda da çıkarılacak.
Boris Yankov

3
  1. Bunların boyutu tahmin edilebilir (yığın hariç sabit ve yığın tipik olarak birkaç MB ile sınırlıdır) ve tipik olarak çok küçük (en azından yüzlerce MB büyük uygulamanın tahsis edebileceği ile karşılaştırıldığında).

  2. Dinamik olarak yerleştirilmiş nesneler tipik olarak erişilebilecekleri küçük bir zaman çerçevesine sahiptir. Ondan sonra, bir daha asla referans alınabilecekleri mümkün değil. Buna, veri bölümündeki girişler, global değişkenler ve bunun gibi bir şey: Sık sık, bunları doğrudan referans veren bir kod parçası var (düşünün const char *foo() { return "foo"; }). Normal olarak, kod değişmez, bu nedenle referans orada kalır ve işlev her çağrıldığında başka bir referans oluşturulur (bu, bilgisayar bildiği kadarıyla herhangi bir zamanda olabilir - durma problemini çözmediğiniz sürece) ). Böylece yapamadı hep ulaşılabilir olacağı gibi, serbest bellek çoğu zaten.

  3. Çöp toplayan birçok dilde, çalıştırılmakta olan programa ait olan her şey yığın tahsis edilir. Python'da herhangi bir veri bölümü yoktur ve yığın tahsisli değer yoktur (yerel değişkenlerin referansları vardır ve çağrı yığını vardır, ancak hiçbiri intC ile aynı anlamda bir değer değildir ). Her nesne öbek üzerinde.


"Python'da, herhangi bir veri bölümü yok". Bu kesinlikle doğru konuşmuyor. Hiçbiri, Doğru ve Yanlış, anladığım gibi veri bölümünde tahsis edilmiştir: stackoverflow.com/questions/7681786/how-is-hashnone-calculated
Jason Baker

@ JasonBaker: İlginç bul! Yine de herhangi bir etkisi yoktur. Bir uygulama detayı ve yerleşik nesnelerle sınırlı. Bu, söz konusu nesnelerin , program ömrü boyunca hiç bir zaman tahsis edilmesinin beklenmediğini , aynı zamanda küçük olmadıklarını (her biri 32 bayttan daha az olduğunu sanmıyorum) bahsetmiyorum .

@delnan Eric Lippert'i işaret etmek ister, çoğu dil için yığın ve yığın için ayrı bellek bölgelerinin varlığı bir uygulama detayıdır. Çoğu dili hiç yığın kullanmadan uygulayabilirsiniz (ne zaman performansınız performans sergileyebilirse de) ve yine de özelliklerine uygun
Jules

2

Bazı diğer cevap verenlerin söylediği gibi, yığın kök dizinin bir parçasıdır, bu nedenle referanslar için taranır, ancak "toplanmaz".

Sadece yığındaki çöpün önemli olmadığını ima eden bazı yorumlara cevap vermek istiyorum; yapar, çünkü öbek üzerinde daha fazla çöpün ulaşılabilir olarak kabul edilmesine neden olabilir. Vicdani VM ve derleyici yazarları, yığının ölü parçalarını taramanın dışında ya da geçersiz kılar. IIRC, bazı sanal makinelerde bilgisayar aralıklarını yığın yuvası canlılık bitmaplerine eşleyen tablolara sahiptir ve diğerleri yuvaları boşaltır. Şu anda hangi tekniğin tercih edildiğini bilmiyorum.

Bu özel düşünceyi tanımlamak için kullanılan bir terim alan için güvenlidir .


Bilmek ilginç olurdu. İlk düşünce, boşlukları boşaltmanın en gerçekçi olduğu düşüncesidir. Dışlanan alanlardan oluşan bir ağacın arasında gezinmek, sadece boşları taramaktan daha uzun sürebilir. Açıkçası, yığını sıkıştırmak için yapılan herhangi bir girişim tehlikeyle doludur! Bu işi yapmak, zihin bükme / hataya eğilimli bir işlem gibi gözüküyor.
Brian Knoblauch

@Brian, Aslında, biraz daha düşününce, yazılı bir VM için yine de buna benzer bir şeye ihtiyacınız var, böylece hangi slotların tam sayıların, kayan noktaların vs. olduğu gibi referanslar olduğunu belirleyebilirsiniz. Argümanlarını CONS değil "Henry Baker.
Ryan Culpepper

Yuva tiplerini belirlemek ve uygun bir şekilde kullanıldıklarını doğrulamak derleme zamanında (güvenilir bytecode kullanan VM'ler için) veya yükleme süresi (bytecode güvenilir olmayan bir kaynaktan, örneğin Java'dan geldiğinde) statik olarak yapılabilir ve yapılır.
Jules

1

Sizin ve diğer birçok kişinin yanlış anladığı bazı temel yanlış anlamaları belirteyim:

“Neden Çöp Toplama sadece yığını temizliyor?” Bu tam tersi. Yalnızca en basit, en muhafazakar ve en yavaş çöp toplayıcıları yığını temizler. Bu yüzden çok yavaşlar.

Hızlı çöp toplayıcıları yalnızca yığını (ve isteğe bağlı olarak FFI işaretçileri için bazı küreseller ve canlı işaretçiler için olan kayıtlar gibi bazı diğer kökleri) temizler ve yalnızca yığın nesneleri tarafından erişilebilen işaretleyicileri kopyalar. Gerisi hiç atılmıyor, atılıyor (yani yok sayılıyor).

Yığın yığınlardan yaklaşık 1000 kat daha büyük olduğu için, böyle bir yığın tarama GC tipik olarak çok daha hızlıdır. Normal büyüklükteki yığınlarda ~ 15ms vs 250ms. Nesneleri bir uzaydan diğerine kopyaladığından (taşırken), çoğunlukla yarı boşluklu bir kopya toplayıcı olarak adlandırılır, 2x belleğe ihtiyaç duyar ve bu nedenle çoğunlukla çok küçük aygıtlarda kullanılamaz, çok fazla belleği olmayan telefonları sever. Sıkıştırıyor, bu nedenle basit işaretleme ve tarama yığını tarayıcılarının aksine, önbellek dostu bir yazılımdır.

İşaretçiler hareket ettiğinden, FFI, kimlik ve referanslar zordur. Kimlik genellikle rastgele kimliklerle, yönlendirme işaretçileriyle yapılan referanslarla çözülür. FFI aldatıcıdır, çünkü yabancı nesneler eski alandaki işaretçileri geri alamaz. FFI işaretçiler genellikle ayrı bir yığın arenasında tutulur, örneğin yavaş bir işaret ve süpürme, statik toplayıcı ile. Ya da yeniden ifade ile önemsiz malloc. Malloc'un çok büyük bir yükü olduğunu ve daha da fazla tazelediğini unutmayın.

Mark & ​​sweep'in uygulanması çok önemlidir, ancak gerçek programlarda kullanılmamalıdır ve özellikle standart toplayıcı olarak öğretilmemelidir. Bu hızlı yığın taramalı fotokopi toplayıcıların en ünlüsü, Cheney İki Parmaklı Kollektör olarak adlandırılır .


Soru, belirli çöp toplama algoritmalarından ziyade, hangi bellek parçalarının çöp toplandığı hakkında daha fazla görünüyor. Son cümle, özellikle OP'nin, "çöp toplama" için, çöp toplama işlemini uygulamak için belirli bir mekanizma yerine, "çöp toplama" için eşanlamlı bir eş anlamlı olarak kullandığı anlamına gelir. Bunu göz önüne alarak, cevabınız yalnızca en basit çöp toplayıcı çöpleri yığın toplar ve hızlı çöp toplayıcıları yığın yığınını ve statik belleği toplayarak, yığının bellek tükenene kadar büyümesine ve büyümesine izin verdiğini söyleyerek cevap verir.
8,

Hayır, soru çok özel ve zekiydi. Cevaplar öyle değil. Yavaş işaret ve süpürme GC'lerinin iki fazı vardır; işaretleme aşaması istif üzerindeki kökleri taramaktadır ve süpürme fazı yığını taramaktadır. Hızlı kopyalama GC'lerin yalnızca bir aşaması vardır, yığını tarar. Bu kadar kolay. Görünüşe göre burada kimse doğru çöp toplayıcılarını bilmiyor, çünkü sorunun yanıtlanması gerekiyor. Yorumunuz çılgınca kapalı.
Rurban

0

Yığına ne ayrılır? Yerel değişkenler ve dönüş adresleri (C cinsinden). Bir işlev döndüğünde, yerel değişkenler atılır. Yığını süpürmek gerekli değildir, hatta zararlı olabilir.

Pek çok dinamik dil ve ayrıca Java veya C #, genellikle C dilinde bir sistem programlama dilinde uygulanır, Java'nın C işlevleriyle uygulandığını ve C yerel değişkenlerini kullandığını ve bu nedenle Java'nın çöp toplayıcısının yığını taramasına gerek olmadığını söyleyebilirsiniz.

İlginç bir istisna vardır: Tavuk Sistem'in çöp toplayıcı süpürme yapar onun uygulanması bir çöp toplama birinci nesil alanı olarak yığını kullanması nedeniyle, (bir bakıma) yığını: bkz Tavuk Şema Tasarım Wikipedia .

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.