Java nesneleri neden artık başvuruda bulunulduktan hemen sonra silinmiyor?


77

Java'da, bir nesne artık referanslara sahip olmaz, silinmeye uygun hale gelir, ancak JVM, nesnenin gerçekte ne zaman silineceğine karar verir. Objective-C terminolojisini kullanmak için, tüm Java referansları doğal olarak "güçlü" dür. Bununla birlikte, Objective-C'de bir nesnenin artık güçlü referansları yoksa, nesne derhal silinir. Neden Java’da durum böyle değil?


46
Java nesnelerinin gerçekte ne zaman silineceğini önemsememeniz gerekir . Bu bir uygulama detayıdır.
Basile Starynkevitch

154
@BasileStarynkevitch Sisteminizin / platformunuzun çalışmasına kesinlikle dikkat etmeli ve meydan okumalısınız. 'Nasıl' ve 'neden' sorularını sormak, daha iyi bir programcı olmanın en iyi yollarından biridir (ve daha genel anlamda daha akıllı bir kişi).
Artur Biesiadowski

6
Objective C dairesel referanslar olduğunda ne yapar? Sanırım sadece onları sızdırıyor?
Mehrdad,

45
@ArturBiesiadowksi: Hayır, Java özelliği bir nesnenin ne zaman silindiğini söylemez (ve R5RS için de aynı şekilde ). Sen ve muhtemelen Java programı geliştirmelidir olabilir eğer-olarak bu silme olur hiç (ve bir Java Merhaba dünya gibi kısa ömürlü işlemleri için, gerçekten de olmaz). Farklı bir hikaye olan canlı nesneler kümesini (veya hafıza tüketimini) önemseyebilirsiniz.
Basile Starynkevitch

28
Bir gün acemi ustaya "Tahsis sorunumuza bir çözümüm var. Her tahsise bir referans sayımı vereceğiz ve sıfıra ulaştığında nesneyi silebiliriz" dedi. Usta, "Aceminin efendiye söylediği bir gün" cevabını verdi ...
Eric Lippert

Yanıtlar:


79

Öncelikle, Java zayıf referanslara ve yumuşak referanslar denilen başka bir en iyi çaba kategorisine sahiptir. Zayıf ve güçlü referanslar, referans sayımına göre çöp toplama işleminden tamamen ayrı bir konudur.

İkincisi, bellek kullanımında zamandan ödün vermeden çöp toplama işlemini daha verimli hale getirebilecek desenler vardır. Örneğin, daha yeni nesnelerin silinmesi daha eski nesnelere göre daha muhtemeldir. Böylece, taramalar arasında biraz beklerseniz, hayatta kalan birkaç kişiyi daha uzun süreli depolamaya taşıdığınızda yeni nesil belleğin çoğunu silebilirsiniz. Bu uzun süreli depolama çok daha az sıklıkla taranabilir. Manuel bellek yönetimi veya referans sayımı yoluyla derhal silinmesi, parçalanmaya daha yatkındır.

Maaş başına bir kez bakkal alışverişine gidip her gün bir gün yetecek kadar yiyecek alabilmek arasındaki fark gibi. Büyük bir seyahatiniz küçük bir seyahatten çok daha uzun sürer, ancak genel olarak zamandan ve muhtemelen paradan tasarruf edersiniz.


58
Bir programcının karısı onu markete gönderir. Ona "Bir somun ekmek al, biraz yumurta görürsen bir düzine kap" der. Programcı daha sonra kolunun altında bir düzine somun ekmekle geri döner.
Neil,

7
Yeni nesil gc zamanının genellikle canlı nesnelerin miktarıyla orantılı olduğunu belirtmekteyim , bu nedenle daha fazla silinen nesneye sahip olmak, çoğu durumda maliyetlerinin hiç ödenmeyeceği anlamına gelir. Silme işlemi, hayatta kalan alan işaretçisini çevirmek ve isteğe bağlı olarak tüm bellek alanını tek bir büyük küme içinde sıfırlamak kadar basit (gc'nin sonunda yapıldığından emin olun veya tlabs'ların veya nesnelerin mevcut jvms'de tahsis edilmesi sırasında amortize edildiğinden emin değilsiniz)
Artur Biesiadowski

64
@Neil bu 13 somun olmamalıdır?
JAY

67
"7 numaralı koridorda bir hatayla kapalı"
joeytwiddle

13
@JAD 13 derdim, ama çoğu bunu alma eğiliminde değil. ;)
Neil

86

Çünkü bir şeyi doğru bir şekilde bilmek artık referans gösterilmemektedir. Kolaylığa yakın bile değil.

Ya birbirlerine atıfta bulunan iki nesneniz varsa? Sonsuza dek kalırlar mı? Bu düşünme hattını herhangi bir keyfi veri yapısını çözmek üzere genişletmek; yakında JVM'nin veya diğer çöp toplayıcılarının neye ihtiyaç duyulduğunu ve neyin gidebileceğini belirlemek için çok daha karmaşık yöntemler kullanmaya zorlandığını göreceksiniz.


7
Ya da olabildiğince fazla geri ödemeyi kullandığınız, bellek sızdıran dairesel bağımlılıklar olduğunu düşündüğünüzde GC’ye başvurduğunuzda Python yaklaşımını kullanabilirsiniz. GC'ye ek olarak neden yeniden hesaplayamadıklarını anlamıyorum?
Mehrdad

27
@Mehrdad Onlar yapabilirdi. Ama muhtemelen daha yavaş olurdu. Hiçbir şey bunu uygulamanıza engel olamaz, ancak Hotspot veya OpenJ9'daki GC'lerin hiçbirini geçmeyi beklemeyin.
Josef

21
@ jpmc26 çünkü artık kullanılmadıkları andan itibaren nesneleri silerseniz, olasılık yüksek, onları daha da artıran yüksek bir yük durumunda silersiniz. GC daha az yük olduğunda çalışabilir. Referansın kendisinin sayılması, her referans için küçük bir masraftır. Ayrıca, bir GC ile genellikle tek bir nesneyi kullanmadan hiçbir referansı olmayan büyük bir bellek bölümünü atabilirsiniz.
Josef

33
@Josef: uygun referans sayımı da ücretsiz değil; referans sayısı güncellemesi , özellikle modern çok çekirdekli mimarilerde şaşırtıcı derecede pahalı olan atomik artışlar / azalmalar gerektirir . CPython'da bu bir problem değildir (CPython kendi başına oldukça yavaştır ve GIL, çoklu okuma performansını tek çekirdekli seviyelerle sınırlandırır), ancak paralelliği destekleyen daha hızlı bir dilde sorun olabilir. PyPy'nin referans sayısından tamamen kurtulması ve sadece GC kullanması bir şans değil.
Matteo Italia

10
@Mehrdad, Java için GC sayma referansınızı uyguladıktan sonra, diğer tüm GC uygulamalarından daha kötü performans gösterdiği bir durumu bulmak için memnuniyetle test edeceğim.
Josef

45

AFAIK, JVM belirtimi (İngilizce olarak yazılmıştır), bir nesnenin (veya bir değerin) ne zaman tam olarak silinmesi gerektiğinden bahsetmez ve bunu uygulamaya ( R5RS için de ) bırakır . Bir şekilde bir çöp toplayıcıyı gerektirir veya önerir ancak detayları uygulamaya bırakmaktadır. Ve aynı şekilde Java özelliği için.

Unutmayın programlama dilleri olan özellikler (bir sözdizimi , semantik , değil yazılım uygulamaları, vb ...). Java (veya JVM) gibi bir dilin birçok uygulaması vardır. Özelliği yayınlanabilir , indirilebilir (böylece çalışabilirsiniz) ve İngilizce olarak yazılır. §2.3.3 JVM şartnamelerinin bir kısmı bir çöp toplayıcısından bahseder:

Nesneler için yığın depolama, otomatik bir depolama yönetim sistemi (çöp toplayıcı olarak bilinir) tarafından geri kazanılır; nesneler asla açıkça tahsil edilmez. Java Sanal Makinesi, belirli bir otomatik depolama yönetim sistemi türü olmadığını varsaymaktadır.

(vurgu benimdir; BTW sonlandırması, Java'nın §12.6'sında ve bir bellek modelinin, Java'nın §17.4'ünde belirtilmiştir)

Bu yüzden, (Java'da) bir nesnenin ne zaman silineceğini umursamamanız ve ( eğer görmezden geldiğiniz bir soyutlamayı düşünerek) gerçekleşmiyormuş gibi kodlayabilmeniz gerekir . Elbette farklı bir soru olan hafıza tüketimini ve canlı nesneler kümesini önemsemelisin . Bazı basit durumlarda ("merhaba dünyası" programını düşünün), kendinizi tahsis edebileceğinizi veya tahsis edilen hafızanın oldukça küçük (örneğin bir gigabayttan daha az) olduğuna ikna edebileceğinizi kanıtlayabilirsiniz. tek tek nesnelerin silinmesi . Daha fazla durumda, kendinizi yaşayan nesnelerin olduğuna ikna edebilirsiniz.(ya da ulaşılabilir olanlar, yaşayanlar hakkında mantıklı olan bir süperset) asla makul bir sınırı aşmazlar (ve daha sonra GC'ye güvenirsiniz, ancak çöp toplamanın nasıl ve ne zaman olacağını umursamazsınız). Uzay karmaşıklığı hakkında okuyun .

Merhaba dünya gibi kısa ömürlü bir Java programını çalıştıran birkaç JVM uygulamasında, çöp toplayıcı hiç tetiklenmiyor ve silme gerçekleşmiyor. AFAIU, böyle bir davranışın sayısız Java spesifikasyonuna uyuyor.

Çoğu JVM uygulaması kuşak kopyalama tekniklerini kullanır (en azından çoğu Java nesnesi için, sonlandırmayı veya zayıf referansları kullanmayanları ; sonlandırmanın kısa sürede gerçekleşmesi garanti edilmez ve ertelenebilir, bu nedenle kodunuzun yazmaması gereken yararlı bir özelliktir). Tek bir nesneyi silme nosyonunun bir anlam ifade etmemesine bağlıdır (çünkü birçok nesne için büyük miktarda bellek içeren bellek bölgeleri - belki de birkaç megabayt bir kerede serbest bırakılır).

Eğer JVM spesifikasyonu her bir nesnenin mümkün olan en kısa sürede silinmesini gerektiriyorsa (veya sadece nesne silme konusunda daha fazla kısıtlama koyarsa), verimli nesil GC teknikleri yasaklanmış olacak ve Java ile JVM tasarımcıları bundan kaçınmakta akıllıca olacaktır.

BTW, nesneleri silmeyen ve belleği serbest bırakmayan saf bir JVM'nin teknik özelliklere uygun olması (mektubu, ruhu değil) ve kesinlikle merhaba dünyası bir şeyi pratikte yürütmesi mümkün olabilir (en çok küçük ve kısa ömürlü Java programları muhtemelen birkaç gigabayttan fazla bellek ayırmaz). Elbette böyle bir JVM, bahsetmeye değmez ve sadece oyuncak bir şeydir ( C'nin bu uygulaması gibi malloc). Daha fazla bilgi için Epsilon NoOp GC'ye bakınız . Gerçek hayattaki JVM'ler çok karmaşık yazılım parçalarıdır ve çok sayıda çöp toplama tekniğini karıştırır.

Ayrıca, Java , JVM ile aynı değildir ve JVM olmadan çalışan Java uygulamalarınız vardır (örneğin, önceden belirlenmiş Java derleyicileri, Android çalışma zamanı ). Gelen bazı durumlarda (çoğunlukla akademik olanlar), size çünkü bir Java programı tahsis veya çalışma (örneğin en silmeyin ki (böylece "derleme zamanı çöp toplama" teknikleri denir) hayal olabilir optimizasyon derleyicisi sadece kullanmak için yeterince zeki olmuş çağrı yığını ve otomatik değişkenler ).

Java nesneleri neden artık başvuruda bulunulduktan hemen sonra silinmiyor?

Çünkü Java ve JVM özellikleri buna ihtiyaç duymuyor.


Daha fazla bilgi için GC el kitabını okuyun (ve JVM spec ). Bir nesne için hayatta (ya da gelecekteki hesaplamalar için yararlı) olmanın bir tüm program (modüler olmayan) özellik olduğuna dikkat edin.

Objective-C hafıza yönetimine referans sayma yaklaşımını tercih eder . Ve bunun ayrıca tuzakları vardır (örneğin, Objective-C programcısı zayıf referansları açıklayarak dairesel referansları dikkate almak zorundadır , ancak bir JVM, dairesel referansları Java programlayıcısına dikkat etmeksizin pratikte güzel bir şekilde ele alır).

Orada hiçbir Silver Bullet (farkında programlama ve programlama dili tasarımında Durma Problemi ; yararlı bir yaşam nesnesi olma olduğu undecidable genel olarak).

Ayrıca SICP , Programlama Dili Pragmatik , Ejderha Kitap , Küçük Parçalar ve İşletim Sistemlerinde Lisp : Üç Kolay Parça da okuyabilirsiniz . Java ile ilgili değillerdir, ancak fikrinizi açacaklar ve bir JVM'nin ne yapması gerektiğini ve pratik olarak (diğer parçalarla) bilgisayarınızda nasıl çalışabileceğini anlamalarına yardımcı olacaktır. Ayrıca, mevcut açık kaynaklı JVM uygulamalarının karmaşık kaynak kodunu ( birkaç milyon kaynak kod satırına sahip olan OpenJDK gibi ) çalışmak için aylarca (ya da birkaç yıl) harcayabilirsiniz .


20
“Nesneleri asla silen ve belleği serbest bırakmayan saf bir JVM'nin özelliklere uygun olması mümkün olabilir” Bu kesinlikle kesinlikle teknik özelliklere uyuyor! Java 11 aslında , diğer şeylerin yanı sıra, çok kısa ömürlü programlar için bir op-çöp toplayıcısı ekliyor .
Michael,

6
"Bir nesnenin ne zaman silineceğini umursamamanız gerekir" Birincisi, RAII'nin artık uygulanabilir bir model olmadığını ve finalizeherhangi bir kaynak yönetimine (dosya tanıtıcıları, db bağlantıları, gpu kaynakları vb.) Güvenemeyeceğinizi bilmelisiniz.
Alexander,

4
@Michael Kullanılan bellek tavanı ile toplu işleme için mükemmel bir anlam ifade eder. İşletim sistemi sadece "bu program tarafından kullanılan tüm bellek şimdi gitti!" Diyebilir. Sonuçta, bu oldukça hızlı. Aslında, C'deki birçok program, özellikle de Unix dünyasında, bu şekilde yazılmıştır. Pascal, "güvensiz / işaretçi işaretini önceden kaydedilmiş bir kontrol noktasına sıfırla" gibi güzel bir şekilde korkunçtu, aynı şeyi yapmanıza izin verdi, ancak oldukça güvensizdi, alt görevi başlattı, sıfırladı.
Luaan

6
Genel olarak C ++ dışındaki (ve ondan kasıtlı olarak çıkan birkaç dilde) @Alexander, RAII'nin yalnızca sonlandırıcılara dayanarak çalışacağını varsayarak, açık bir kaynak kontrol bloğuna karşı uyarılması ve değiştirilmesi gereken bir anti-kalıptır. GC'nin asıl amacı, ömrünün ve kaynağın, sonuçta çözülmüş olmasıdır.
Leushenko

3
@Leushenko Ben "yaşam ve kaynak ayrıştırılır" GC'nin "bütün noktası" olduğunu kesinlikle katılmıyorum. GC'nin ana noktası için ödediğiniz negatif fiyat: kolay, güvenli bellek yönetimi. "RAII'nin yalnızca finalizörlere dayanarak çalışacağını varsaymak bir anti-paterndir" Java ile mi? Belki. Ancak CPython, Rust, Swift veya Objective C'de "uyarılmadı ve yerini değiştiren açık bir kaynak kontrol bloğu olan" Hayır, uyarılmadı, bunlar kesinlikle daha sınırlı. Bir kaynağı RAII aracılığıyla yöneten bir nesne, kapsamlı yaşamı dolaştırmak için size bir tutamaç verir. Kaynaklı bir deneme bloğu, tek bir kapsamla sınırlıdır.
Alexander,

23

Objective-C terminolojisini kullanmak için, tüm Java referansları doğal olarak "güçlü" dür.

Bu doğru değil - Java, dil anahtar kelimeleri yerine nesne düzeyinde uygulanmasına rağmen, hem zayıf hem de yumuşak referanslara sahiptir.

Objective-C'de, bir nesne artık güçlü referanslara sahip değilse, nesne derhal silinir.

Bu da mutlaka doğru değildir - Objective C'nin bazı versiyonları gerçekten bir nesil çöp toplayıcı kullandı. Diğer sürümlerde hiç çöp toplama yapılmamıştır.

Objective C'nin daha yeni sürümlerinin, iz tabanlı bir GC yerine otomatik referans sayma (ARC) kullandığı doğrudur ve bu (genellikle) bu referans sayısı sıfıra ulaştığında nesnenin "silinmesine" neden olur. Bununla birlikte, bir JVM uygulamasının da uyumlu olabileceğini ve tam olarak bu şekilde çalışabileceğini unutmayın (heck, uyumlu olabilir ve hiç GC'si olmayabilir).

Öyleyse neden çoğu JVM uygulaması bunu yapmıyor ve bunun yerine iz tabanlı GC algoritmaları kullanıyor?

Basitçe söylemek gerekirse, ARC ilk göründüğü kadar ütopik değildir:

  • Bir referans her kopyalandığında, değiştirildiğinde veya kapsamın dışına çıktığında bir sayacı artırmanız veya azaltmanız gerekir; bu da genel bir performansa neden olur.
  • ARC döngüsel referansları kolayca temizleyemez, çünkü hepsinin içinde bir referans vardır, bu nedenle referans sayıları asla sıfıra düşmez.

ARC'nin elbette avantajları vardır - uygulanması ve toplanması basittir, deterministtir. Ancak, diğerlerinin yanı sıra, yukarıdaki dezavantajlar, JVM uygulamalarının çoğunluğunun kuşak, iz tabanlı bir GC kullanması nedenidir.


1
İşin garibi, Apple'ın ARC'ye geçtiği, çünkü pratikte diğer GC'lerden (özellikle de nesiller) daha büyük performans gösterdiğini görüyorlardı. Adil olmak gerekirse, bu çoğunlukla bellek kısıtlı platformlarda (iPhone) doğrudur. Ancak, “ARC ilk göründüğü kadar ütopik değildir” ifadesine, nesiller (ve deterministik olmayan diğer) GC'lerin ilk göründüğü kadar ütopya olmadığını söyleyerek karşı koyarım: Deterministik yıkım, muhtemelen Senaryoların büyük çoğunluğu.
Konrad Rudolph

3
@KonradRudolph daha çok deterministik bir yıkım hayranı olsam da, “senaryoların büyük çoğunluğunda daha iyi bir seçenek” olduğunu sanmıyorum. Gecikme veya hafızanın ortalama verimden daha önemli olduğu ve özellikle de mantık oldukça basit olduğunda kesinlikle daha iyi bir seçenektir. Ancak, çok sayıda döngüsel referans vb. Gerektiren ve hızlı bir ortalama işlem gerektiren pek çok karmaşık uygulama olmadığı gibi değil, ancak gecikmeyi gerçekten önemsemiyor ve bol miktarda bellek var. Bunlar için, ARC'nin iyi bir fikir olup olmadığı şüphelidir.
leftaroundabout

1
@leftaroundabout “Çoğu senaryoda”, ne verimlilik ne de bellek baskısı bir darboğazdır, bu yüzden hiçbir şekilde önemi yoktur. Örneğiniz belirli bir senaryodur. Kabul ediyorum, çok nadir değil, ancak ARC'nin daha uygun olduğu diğer senaryolardan daha yaygın olduğunu iddia ettiği kadar ileri gitmem. Ayrıca, ARC sadece iyi döngüleri ile başa çıkabilir . Sadece programcının basit ve manuel müdahalelerini gerektirir. Bu, onu daha az ideal hale getirir, ancak bir anlaşma kırıcı değildir. Deterministik sonlandırmanın sizin yaptığınızdan çok daha önemli bir özellik olduğunu iddia ediyorum.
Konrad Rudolph

3
@KonradRudolph ARC, programcının basit bir manuel müdahalesini gerektiriyorsa, bu çevrimlerle ilgilenmez. İki kat bağlantılı listeleri yoğun şekilde kullanmaya başlarsanız, ARC manuel bellek tahsisine girer. Büyük isteğe bağlı grafikleriniz varsa, ARC sizi bir çöp toplayıcı yazmaya zorlar. GC argümanı, yıkıma ihtiyaç duyan kaynakların bellek alt sisteminin işi olmadığı ve göreceli olarak az sayıda kişiyi takip etmek için, programcının basit bir manuel müdahalesiyle belirleyici bir şekilde sonlandırılması gerektiğidir.
prosfilaes

2
@KonradRudolph ARC ve çevrimleri temelde elle kullanılmazlarsa bellek sızıntılarına yol açar. Yeterince karmaşık sistemlerde, örneğin bir haritada depolanan bazı nesneler bu haritaya bir referans depolarsa, bir haritanın kod oluşturmasından ve bu haritayı imha etmekten sorumlu olmayan bir programcı tarafından yapılabilecek bir değişiklik yapması durumunda büyük sızıntılar meydana gelebilir. Büyük isteğe bağlı grafikler, iç işaretçilerin güçlü olmadığı, bağlantılı nesnelerin kaybolması gerektiği anlamına gelmediği anlamına gelmez. Bir miktar bellek sızıntısı ile başa çıkmak, dosyaları manuel olarak kapatmak zorunda kalmaktan daha az sorun olup olmadığını söyleyemem ama gerçek.
prosfilaes

5

Java, nesnelerin ne zaman toplanacağını tam olarak belirtmez, çünkü bu, uygulamalara çöp toplama işleminin nasıl seçileceğini seçme özgürlüğü verir.

Pek çok farklı çöp toplama mekanizması vardır, ancak bir nesneyi hemen toplayabileceğinizi garanti edenler neredeyse tamamen referans sayımına dayanır (Bu eğilimi kıran herhangi bir algoritmadan habersiz). Referans sayımı güçlü bir araçtır, ancak referans sayısının korunmasının bir bedeli vardır. Tekli kodda, bir artırma ve azaltma işleminden başka bir şey değildir, bu nedenle bir işaretçi atamak, referans sayılan kodda referans sayılan kodda olduğundan 3 kat daha fazla maliyete mal olabilir (derleyici makineye her şeyi yapabilirse kodu).

Çok iş parçacıklı kodda, maliyet yüksektir. Her ikisi de pahalı olabilen atomik artışlar / azalmalar ya da kilitler gerektirir. Modern bir işlemcide, bir atomik işlem basit bir kayıt işleminden 20 kat daha pahalı olabilir (açıkçası işlemciden işlemciye kadar değişebilir). Bu maliyeti artırabilir.

Bu sayede birkaç modelin yaptığı takasları değerlendirebiliriz.

  • Objective-C, ARC - otomatik referans sayımına odaklanır. Onların yaklaşımı, her şey için referans sayımı kullanmaktır. Döngü tanıma (bildiğim kadarıyla) yoktur, bu nedenle programcıların döngülerin oluşmasını engellemesi beklenir, bu da geliştirme süresini kısaltır. Teorileri, işaretçilere sık sık atanmadıkları ve derleyicileri, referans sayımlarını artırma / azaltma işleminin bir nesnenin ölmesine neden olamayacağı durumları belirleyebilir ve bu artışları / azalmaları tamamen ortadan kaldırabilir. Böylece referans sayma maliyetini minimize ederler.

  • CPython melez bir mekanizma kullanır. Referans sayıları kullanıyorlar, ancak döngüleri tanımlayan ve serbest bırakan bir çöp toplayıcıları da var. Bu, her iki yaklaşımın da pahasına, her iki dünyanın da faydalarını sağlar. CPython referans sayıları korumak zorundadır hem veKitabı döngüleri tespit etmek için saklayın. CPython bununla iki şekilde kurtulur. İlk yumruk CPython'un tamamen okuyuculu olmamasıdır. Gith olarak bilinen ve çok iş parçacığı sınırlayan bir kilidi vardır. Bu, CPython'un atomik olanlardan ziyade normal artışları / azalışları kullanabileceği anlamına gelir, bu daha hızlıdır. CPython da yorumlanır, yani bir değişkene atanma gibi işlemler zaten 1 yerine bir avuç komut alır. Bu, C kodunda hızlı bir şekilde yapılan artım / azalış işlemlerinin ekstra maliyetidir, çünkü bu bedeli zaten ödedim.

  • Java, referans sayılan bir sistemi hiç garanti etmeme yaklaşımını ortadan kaldırıyor. Nitekim şartname, nesnelerin nasıl yönetildiğiyle ilgili otomatik bir depolama yönetim sistemi olacağına dair hiçbir şey söylemez . Bununla birlikte, bu spesifikasyon ayrıca, bunun döngüleri idare edecek şekilde toplanan çöp olacağı varsayımına da şiddetle işaret etmektedir. Nesnelerin ne zaman biteceğini belirtmemekle java, artan / azalan zaman kaybetmeyen koleksiyoncuları kullanma özgürlüğüne sahip olur. Aslında, nesiller çöp toplayıcıları gibi akıllı algoritmalar, geri kazanılan verilere bakmadan bile birçok basit vakayı bile idare edebiliyor (sadece hala referans alınan verilere bakmak zorundalar).

Bu yüzden, bu üçünün de haksız yere gideceğini görebiliyoruz. Hangi tradeoff en iyisi dilin nasıl kullanılması amaçlandığına bağlıdır.


4

Her ne kadar finalizeJava'nın GC'sine domuzcukla desteklenmiş olsa da, çekirdeğindeki çöp toplama ölü nesnelerle değil canlı olanlarla da ilgilidir. Bazı GC sistemlerinde (belki de bazı Java uygulamaları da dahil olmak üzere), bir nesneyi temsil eden bir demet parçayı, hiçbir şey için kullanılmayan bir demet depodan ayıran tek şey, birincisine yapılan referansların varlığı olabilir. Sonlandırıcılı nesneler özel bir listeye eklenirken, diğer nesnelerin evrende depolarının, kullanıcı kodunda tutulan referanslar dışında bir nesne ile ilişkili olduğunu söyleyen hiçbir şeyi olmayabilir. Bu son referansın üzerine yazıldığında, bellekteki bit deseni evrendeki herhangi bir şeyin farkında olup olmadığını farketmeksizin hemen bir nesne olarak tanınabilir hale gelir.

Çöp toplama işleminin amacı, referansların bulunmadığı nesneleri yok etmek değil, üç şeyi başarmaktır:

  1. Onlarla ilişkilendirilmiş, ulaşılabilir referanslara sahip olmayan nesneleri tanımlayan zayıf referansları geçersiz kılın.

  2. Bunlardan herhangi birinin güçlü bir şekilde erişilebilen referansları olup olmadığını görmek için sistemin sonlandırıcı içeren nesneler listesinde arama yapın.

  3. Herhangi bir nesne tarafından kullanılmayan depolama bölgelerini belirleyin ve birleştirin.

GC'nin birincil hedefinin 3 olduğunu ve bunu yapmadan önce ne kadar uzun süre beklediğini, birinin konsolidasyonda daha fazla fırsat bulmasının olası olduğunu unutmayın. Biri depolamayı hemen kullanabileceği durumlarda # 3 yapmak mantıklıdır, ancak aksi halde ertelemek daha mantıklı olur.


5
Aslında, gc'nin tek bir amacı var: Sonsuz hafızayı simüle etmek. Hedef olarak adlandırdığınız her şey, soyutlamada bir kusur veya bir uygulama detayıdır.
Deduplicator

@Deduplicator: Zayıf referanslar, GC yardımı olmadan elde edilemeyen yararlı bir anlam sunar.
supercat,

Elbette, zayıf referansların faydalı bir semantiği var. Peki, simülasyon daha iyi olsaydı bu anlambilim gerekli olurdu?
Deduplicator

@Deduplicator: Evet. Güncellemelerin numaralandırma ile nasıl etkileşime gireceğini tanımlayan bir koleksiyon düşünün. Böyle bir koleksiyonun herhangi bir canlı numaralandırıcıya zayıf referanslar vermesi gerekebilir. Sınırsız bellek sisteminde tekrar tekrar tekrarlanan bir koleksiyon, ilgilenilen numaralandırıcıların listesini sınırlandırmadan büyütür. Bu liste için gereken bellek sorun olmaz, ancak yinelemek için gereken zaman sistem performansını düşürür. GC eklemek, bir O (N) ve O (N ^ 2) algoritması arasındaki farkı ifade edebilir.
supercat,

2
Bir listeye eklemek ve kullanıldıklarında kendilerini aramalarını sağlamak yerine neden numaralandırıcıları bilgilendirmek istiyorsunuz? Ve çöp baskısına bağlı olmak yerine, zamanında işlenen çöplere bağlı herhangi bir program, eğer hareket ederse, zaten günah halinde yaşamaktadır.
Deduplicator

4

Sorunuzun yeniden değerlendirilmesini ve genelleştirilmesini önereyim:

Java neden GC süreci hakkında güçlü garantiler vermiyor?

Bunu akılda tutarak, burada cevaplar arasında hızlı bir şekilde ilerleyin. Şu ana kadar yedi tane var (bunu saymıyorsunuz), çok az sayıda yorum dizisi var.

Budur Cevabınız.

GC zor. Çok fazla kaygılar, çok farklı değişimler ve sonuçta çok farklı yaklaşımlar var. Bu yaklaşımlardan bazıları, ihtiyaç duyulmadığında GC'yi bir nesneyi mümkün kılar; diğerleri yapmaz. Sözleşmeyi gevşek tutarak, Java uygulayıcılarına daha fazla seçenek sunar.

Elbette ki bu kararda bile bir haksızlık var: sözleşmeyi gevşek tutarak, Java çoğunlukla * programcıların yıkıcılara güvenme yeteneğini ortadan kaldırıyor. Bu, C ++ programcılarının özellikle özledikleri bir şeydir ([kaynak belirtilmeli];)), bu yüzden önemsiz bir değişmezlik. Bu özel meta kararın bir tartışmasını görmedim, ancak muhtemelen Java halkı, daha fazla GC seçeneğine sahip olmanın faydalarının, programcılara bir nesnenin ne zaman imha edileceğini tam olarak söyleyebilmenin yararlarından ağır basacağına karar verdi.


* finalizeYöntem var, ancak bu cevabın kapsamı dışında kalan çeşitli nedenlerle, ona güvenmek zor ve iyi bir fikir değil.


3

Geliştirici tarafından yazılmış açık kod olmadan hafızayı işlemenin iki farklı stratejisi vardır: Çöp toplama ve referans sayma.

Çöp toplama, geliştirici aptalca bir şey yapmadıkça "çalışma" avantajına sahiptir. Referans sayımında referans döngülerine sahip olabilirsiniz, bu "işe yaradığı" anlamına gelir, ancak geliştirici bazen akıllı olmalıdır. Yani bu çöp toplama için bir artı.

Referans sayımında, referans sayısı sıfıra düştüğünde nesne derhal gider. Referans sayımı için bir avantaj.

Çöp toplama hayranlarına inanıyorsanız, hızlı bir şekilde çöp toplama işlemi, referans sayma hayranlarına inanıyorsanız, referans sayma daha hızlı olur.

Aynı hedefe ulaşmak için sadece iki farklı yöntem var, Java bir yöntem seçti, Objective-C başka birini seçti (ve onu bir eşek ağrısından geliştiriciler için az işe yarar bir şeye değiştirmek için birçok derleyici desteği ekledi).

Java'yı çöp toplamadan referans sayıma değiştirmek, çok fazla kod değişikliğine ihtiyaç duyacağından büyük bir girişim olacaktır.

Teoride, Java bir çöp toplama ve referans sayımı karışımı uygulamış olabilir: Eğer referans sayısı 0 ise, o zaman nesneye erişilemez, ancak tam tersi olmaz. Yani olabilir referans sayıları tutmak ve silme nesneleri kendi başvuru sayısı sıfır olduğunda (ve sonra ulaşılamaz referans döngüsü içindeki nesneleri yakalamak için zaman zaman çöp toplama çalıştırın). Dünyaya çöp koleksiyonuna referans sayımı eklemenin kötü bir fikir olduğunu düşünen insanlara 50/50 ayrıldığını ve referans sayımına çöp toplama eklemenin kötü bir fikir olduğunu düşünenlerin olduğunu düşünüyorum. Yani bu olmayacak.

Yani Java olabilir onların başvuru sayısı sıfır olursa hemen nesneleri silmek ve daha sonra ulaşılamayan döngüleri içinde nesneleri silin. Ama bu bir tasarım kararı ve Java buna karşı karar verdi.


Referans sayma ile, programlayıcı çevrimlerle ilgilendiği için sonuçlandırma önemsizdir. Gc ile çevrimler önemsizdir, ancak programcının sonlandırma konusunda dikkatli olması gerekir.
Deduplicator

@Deduplicator Java, bu başvuru sayısı sıfır kez Objective-C ve Swift yılında, nesne ... üzeredir nesnelere güçlü referansları oluşturmak da mümkündür olacaktır (eğer dealloc / Deist içine sonsuz bir döngü koymak sürece) kaybolur.
gnasher729

Deinit'i deist ile değiştiren aptal yazım denetleyicisini farkettim ...
gnasher729

1
Çoğu programcının otomatik yazım düzeltmesinden nefret etmesinin bir nedeni var ... ;-)
Deduplicator

lol ... Dünya, çöp koleksiyonuna referans sayımı eklemenin kötü bir fikir olduğunu düşünen ve referans sayıma eklemek için çöp toplama eklemenin kötü bir fikir olduğunu düşünen insanlar arasında 0,1 / 0,1 / 99,8 oranında bölündüğünü düşünüyorum. çöp toplama gelene kadar günleri saymaya devam edin, çünkü bu ton zaten tekrar
kokuyor

1

Artık bir nesneye referans olmadığında anlama zorluğuyla ilgili diğer tüm performans argümanları ve tartışmalar doğru olsa da, bahsetmeye değer olduğunu düşündüğüm başka bir fikir, böyle bir şeyi düşünen en az bir JVM (azul) olduğudur. Bu, esasen, konuştuğunuz şeyden tamamen benzer şekilde hareket etmeyecek olan silmeyi denemek için referansları sürekli olarak kontrol eden bir vm ipliğine sahip olan paralel gc'yi uygulamaktadır. Temel olarak, öbek etrafına sürekli bakacak ve referans gösterilmeyen herhangi bir belleği geri almaya çalışacaktır. Bu, çok düşük bir performans maliyetine neden olur, ancak temel olarak sıfır veya çok kısa GC sürelerine yol açar. (Sürekli genişleyen yığın boyutu sistem RAM'ini geçmediği ve daha sonra Azul'un kafası karışmaz ve ejderhalar oluyorsa)

TLDR JVM için bunun gibi bir şey var, sadece özel bir jvm ve diğer mühendislik ödünleri gibi dezavantajları var.

Feragatname: Azul'la hiçbir bağım yok, daha önce bir işte kullandık.


1

Sürekli verimi en üst düzeye çıkarmak veya gc gecikmesini en aza indirmek, GC'nin hemen gerçekleşmemesinin en yaygın nedeni olan dinamik gerginliktir. Bazı sistemlerde, 911 acil durum uygulamaları gibi, belirli bir gecikme eşiğini karşılamamak, sitedeki hata işlemlerini tetiklemeye başlayabilir. Diğerlerinde, bir bankacılık ve / veya arbitraj alanı gibi, verimi en üst düzeye çıkarmak çok daha önemlidir.


0

hız

Niçin tüm bunlar devam ediyor, nihayetinde hız yüzünden. İşlemciler sonsuz hızlıysa veya (pratik olması gerekiyorsa), örneğin saniyede 1.000.000.000.000.000.000.000.000.000.000.000 işlem gerçekleştirildiyse, başvurulan nesnelerin silindiğinden emin olmak gibi her operatör arasında çılgınca uzun ve karmaşık şeyler olabilir. Saniyedeki işlem sayısının şu anda doğru olmadığı ve diğer cevapların çoğunun açıkladığı gibi, bunun anlaşılmasının çok karmaşık olduğu ve kaynakların yoğun olarak anlaşılacağı gibi, çöp toplama, programların gerçekte neyi başarmaya çalıştıklarına odaklanabilmesi için var. hızlı bir şekilde


Eminim, fazladan döngüleri kullanmak için bundan daha ilginç yollar bulabiliriz.
Deduplicator
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.