Java'da bellek sızıntısı nasıl oluşturulur?


3223

Az önce bir röportaj yaptım ve Java ile bir bellek sızıntısı yaratmam istendi .
Söylemeye gerek yok, bir tane oluşturmaya nasıl başlayacağına dair hiçbir fikri olmayan oldukça aptal hissettim.

Bir örnek ne olurdu?


275
JVM böcek engelleme - - Java bellek sızdırıyor olamaz ben açıklayan, Java çöp toplayıcısı kullanır söyle ve "bellek sızıntısı" kendi tanımı hakkında biraz daha spesifik olmalarını isteyeceğini oldukça aynı şekilde C / C ++ olabilir. Bir yerde nesneye referans olması gerekir .
Darien

371
Çoğu cevapta insanların bu uç vakaları ve püf noktalarını aradıklarını ve noktayı (IMO) tamamen kaçırdıklarını komik buluyorum. Sadece bir daha asla kullanmayacak olan nesnelere işe yaramaz referanslar tutan kod gösterebilirler ve aynı zamanda bu referansları asla düşürmezler; biri bu vakaların "gerçek" bellek sızıntısı olmadığını söyleyebilir, çünkü hala etraftaki bu nesnelere referanslar vardır, ancak program bu referansları bir daha asla kullanmazsa ve asla düşürmezse, tamamen " gerçek bellek sızıntısı ".
ehabkost

62
Dürüst olmak gerekirse, "Git" hakkında sorduğum benzer sorunun -1'e indirildiğine inanamıyorum. İşte: stackoverflow.com/questions/4400311/… Temelde bahsettiğim bellek sızıntıları OP'ye +200 upvotes alan ve yine de saldırıya geçtim ve "Go" nun aynı sorunu olup olmadığını sormak için hakaret ettim. Her nasılsa, tüm wiki-şeylerinin bu kadar iyi çalıştığından emin değilim.
SyntaxT3rr0r

74
@ SyntaxT3rr0r - darien'nin yanıtı fanatizm değil. bazı JVM'lerin hafızanın sızdırıldığı anlamına gelen hatalara sahip olabileceğini açıkça kabul etti. bu, bellek sızıntılarına izin veren dil spesifikasyonunun kendisinden farklıdır.
Peter Recore

31
@ehabkost: Hayır, eşdeğer değiller. (1) Belleği geri kazanma yeteneğine sahipsiniz, oysa "gerçek sızıntıda" C / C ++ programınız ayrılan aralığı unutur, kurtarmanın güvenli bir yolu yoktur. (2) Profil oluşturma ile sorunu çok kolay bir şekilde tespit edebilirsiniz, çünkü "şişkinlik" in hangi nesneleri içerdiğini görebilirsiniz. (3) “Gerçek bir sızıntı” kesin bir hatadır, ancak sonlandırılana kadar çok sayıda nesneyi etrafında tutan bir program, çalışma şeklinin kasıtlı bir parçası olabilir .
Darien

Yanıtlar:


2309

Saf Java'da gerçek bir bellek sızıntısı (kod çalıştırarak erişilemeyen, ancak yine de bellekte depolanan nesneler) oluşturmanın iyi bir yolu:

  1. Uygulama uzun süren bir iş parçacığı oluşturur (veya daha hızlı sızıntı yapmak için iş parçacığı havuzu kullanın).
  2. İş parçacığı bir sınıfı (isteğe bağlı olarak özel) aracılığıyla yükler ClassLoader.
  3. Sınıf, büyük bir bellek yığını tahsis eder (örneğin new byte[1000000]), statik bir alanda güçlü bir referans depolar ve ardından a ThreadLocal. Ek belleği ayırmak isteğe bağlıdır (sınıf örneğini sızdırmak yeterlidir), ancak sızıntının çok daha hızlı çalışmasını sağlayacaktır.
  4. Uygulama, özel sınıfa veya bu sınıftan ClassLoaderyüklendiği tüm başvuruları temizler .
  5. Tekrar et.

ThreadLocalOracle'ın JDK'sında uygulanma şekli nedeniyle , bu bir bellek sızıntısı oluşturur:

  • Her Threadbirinin threadLocalsiş parçacığı-yerel değerlerini saklayan özel bir alanı vardır .
  • Bu haritadaki her anahtar bir ThreadLocalnesneye zayıf bir referanstır , bu nedenle bu ThreadLocalnesne çöp toplandıktan sonra girişi haritadan kaldırılır.
  • Fakat her bir değer , güçlü bir referans, bu nedenle bir değer (doğrudan ya da dolaylı olarak) noktalar zaman ThreadLocalonun nesnenin anahtar , bu nesne de olacaktır çöp toplama uzun iplik hayatları kadar ne harita çıkarıldı.

Bu örnekte, güçlü referanslar zinciri şöyle görünür:

Threadnesne → threadLocalsharita → örnek sınıf örneği → örnek sınıf → statik ThreadLocalalan → ThreadLocalnesne.

( ClassLoaderSızıntıyı yaratmada gerçekten bir rol oynamaz, bu ek referans zinciri nedeniyle sızıntıyı daha da kötüleştirir: örnek sınıf → ClassLoader→ yüklediği tüm sınıflar. Özellikle JVM uygulamalarında, özellikle daha önce Java 7, çünkü sınıflar ve ClassLoaders doğrudan doğruca tahliye edildi ve asla çöp toplanmadı.)

Bu örüntünün bir varyasyonu, uygulama kaplarının (Tomcat gibi), bir ThreadLocalşekilde kendilerini işaret eden uygulamaları sık sık yeniden uygularsanız, elek gibi bellek sızmasına neden olabilir . Bu, bazı ince nedenlerden dolayı olabilir ve hata ayıklamak ve / veya düzeltmek genellikle zordur.

Güncelleme : Birçok kişi bunu sormaya devam ettiğinden, işte bu davranışı gösteren bazı örnek kodlar .


186
+1 ClassLoader sızıntıları, genellikle verileri dönüştüren 3. taraf kitaplarından (BeanUtils, XML / JSON kodekleri) kaynaklanan JEE dünyasında en yaygın ağrılı bellek sızıntılarından bazılarıdır. Bu, lib uygulamanızın kök sınıf yükleyicisinin dışına yüklendiğinde, ancak sınıflarınıza referansları tuttuğunda (örn. Önbelleğe alarak) olabilir. Uygulamanızı çözdüğünüzde / yeniden konuşlandırdığınızda, JVM uygulamanın sınıf yükleyicisini (ve dolayısıyla yüklediği tüm sınıfları) çöp toplayamaz, bu nedenle yineleme ile uygulama sunucusu sonunda dağıtılır. Eğer şanslıysanız ClassCastException ile bir ipucu olsun zxyAbc zxyAbc için döküm olamaz
earcam

7
tomcat, TÜM yüklü sınıflarda TÜM statik değişkenleri kullanır, tomcat çok fazla veri çekirdeğine ve kötü kodlamaya sahiptir (biraz zaman ayırmanız ve düzeltmeleri göndermeniz gerekir), ayrıca dahili (küçük) nesneler için önbellek olarak tüm akıl almaz ConcurrentLinkedQueue, o kadar küçük ki, ConcurrentLinkedQueue.Node bile daha fazla bellek alır.
bestsss

57
+1: Classloader sızıntıları kabus. Onları anlamaya çalışmak için haftalar geçirdim. Üzücü olan şey, @earcam'ın söylediği gibi, çoğunlukla 3. taraf kütüphanelerinden kaynaklanıyor ve çoğu profiler bu sızıntıları tespit edemiyor. Bu blogda Classloader sızıntıları hakkında iyi ve net bir açıklama var. blogs.oracle.com/fkieviet/entry/…
Adrian M

4
@Nicolas: Emin misin? JRockit varsayılan olarak GC Class nesnelerini yapar ve HotSpot bunu yapmaz, ancak AFAIK JRockit yine de ThreadLocal tarafından başvurulan bir Class veya ClassLoader'ı GC edemez.
Daniel Pryden

6
Tomcat bu sızıntıları sizin için tespit etmeye ve onlar hakkında uyarmaya çalışacaktır: wiki.apache.org/tomcat/MemoryLeakProtection . En son sürüm bazen sizin için sızıntıyı bile düzeltir.
Matthijs Bierman

1212

Statik alan tutma nesnesi referansı [esp final field]

class MemorableClass {
    static final ArrayList list = new ArrayList(100);
}

String.intern()Uzun String Çağrısı

String str=readString(); // read lengthy string any source db,textbox/jsp etc..
// This will place the string in memory pool from which you can't remove
str.intern();

(Kapatılmamış) açık akışlar (dosya, ağ vb.)

try {
    BufferedReader br = new BufferedReader(new FileReader(inputFile));
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

Kapatılmamış bağlantılar

try {
    Connection conn = ConnectionFactory.getConnection();
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

Yerel yöntemlerle ayrılmış bellek gibi JVM'nin çöp toplayıcısından erişilemeyen alanlar

Web uygulamalarında, bazı nesneler, uygulama açıkça durduruluncaya veya kaldırılana kadar uygulama kapsamında saklanır.

getServletContext().setAttribute("SOME_MAP", map);

noclassgcIBM JDK'deki kullanılmayan sınıf çöpü toplamasını önleyen seçenek gibi yanlış veya uygunsuz JVM seçenekleri

Bkz. IBM jdk ayarları .


178
Bağlam ve oturum özelliklerinin "sızıntı" olduğunu kabul etmem. Onlar sadece uzun ömürlü değişkenler. Ve statik son alan az çok sabittir. Belki büyük sabitlerden kaçınılmalıdır, ancak buna bellek sızıntısı demenin adil olduğunu düşünmüyorum.
Ian McLaird

80
(Kapatılmamış) açık akışlar (dosya, ağ vb ...) , kesinleştirme sırasında (bir sonraki GC döngüsünün ardından olacak) close () zamanlanacaktır ( close()genellikle sonlandırıcıda çağrılmaz) çünkü engelleme işlemi olabilir). Kapatmamak kötü bir uygulamadır, ancak bir sızıntıya neden olmaz. Kapatılmamış java.sql.Connection aynı.
bestsss

33
Çoğu aklı başında JVM'lerde, String sınıfının internhashtable içerikleri üzerinde yalnızca zayıf bir referansı varmış gibi görünüyor . Böyle olunca edilir çöp sızıntı düzgün toplanmış olup. (ancak IANAJP) mindprod.com/jgloss/interned.html#GC
Matt B.

42
Statik alan tutma nesnesi referansı [esp son alanı] bir bellek sızıntısı mı ??
Kanagavelu Sugumar

5
@cHao True. Karşılaştığım tehlike, Akışların bellekten sızmasıyla ilgili sorunlar değil. Sorun, onlar tarafından yeterince bellek sızdırılmamasıdır. Çok fazla tutamaç sızdırabilir, ancak yine de bol miktarda belleğiniz vardır. Çöp Toplayıcı, tam bir koleksiyon yapmaktan rahatsız etmemeye karar verebilir, çünkü hala bol miktarda belleği vardır. Bu, sonlandırıcının çağrılmadığı anlamına gelir, bu nedenle tutamaçlarınız biter. Sorun şu ki, sonlandırıcılar (genellikle), sızan akışlardan bellek tükenmeden önce çalıştırılacaktır, ancak bellek dışında bir şey bitmeden çağrılmayabilir.
Patrick M

460

Yapılacak basit bir şey, yanlış (veya var olmayan) bir HashSet kullanmak hashCode()veya equals()daha sonra "kopya" eklemeye devam etmektir . Yinelenenleri olması gerektiği gibi görmezden gelmek yerine, küme yalnızca büyüyecek ve bunları kaldıramayacaksınız.

Bu kötü anahtarların / öğelerin takılmasını istiyorsanız, aşağıdaki gibi statik bir alan kullanabilirsiniz:

class BadKey {
   // no hashCode or equals();
   public final String key;
   public BadKey(String key) { this.key = key; }
}

Map map = System.getProperties();
map.put(new BadKey("key"), "value"); // Memory leak even if your threads die.

68
Aslında, element sınıfı hashCode alıyor ve yanlışsa bile öğeleri bir HashSet'ten kaldırabilirsiniz; sadece set için bir yineleyici alın ve remove yöntemini kullanın, çünkü yineleyici aslında temel girişler üzerinde değil, öğeler üzerinde çalışır. (Eklenmemiş bir hashCode / eşitinin bir sızıntıyı tetiklemek için yeterli olmadığını unutmayın ; varsayılanlar basit nesne kimliğini uygular ve böylece öğeleri alıp normal şekilde kaldırabilirsiniz.)
Donal Fellows

12
@ Söylemeye çalıştığım şey Donal, sanırım, bir bellek sızıntısı tanımına katılmıyorum. Yineleyici kaldırma tekniğinizi (benzetmeye devam etmek için) bir sızıntı altında bir damlama tavası olarak düşünürdüm; sızıntı damlama kabından bağımsız olarak hala mevcuttur.
corsiKa

94
Bunun olduğunu hemfikir değil sadece HashSet başvuruları kaldırmak ve ve presto içinde tekme için GC bekleyin, çünkü bir bellek "sızıntısı"! bellek geri döner.
user541686

12
@ SyntaxT3rr0r, sorunuzu dilde doğal olarak bellek sızıntılarına yol açan bir şey olup olmadığını sordum. Cevap hayır. Bu sorular, bellek sızıntısı gibi bir şey oluşturmak için bir durumun mümkün olup olmadığını sorar. Bu örneklerin hiçbiri, C / C ++ programcısının anlayacağı şekilde bellek sızıntısı değildir.
Peter Lawrey

11
@Peter Lawrey: ayrıca, bunun hakkında ne düşünüyorsunuz: "C dilinde, tahsis ettiğiniz belleği manuel olarak boşaltmayı unutmazsanız, doğal olarak bellek sızıntısına sızan hiçbir şey yok" . Bu entelektüel sahtekârlık için nasıl olurdu? Her neyse, yorgunum: son sözü söyleyebilirsin.
SyntaxT3rr0r

271

Aşağıda, standart unutulmuş dinleyiciler, statik referanslar, hashmaps'ta sahte / değiştirilebilir anahtarların yanı sıra yaşam döngüsünü sona erdirme şansı olmadan sıkışmış iş parçacıklarının yanı sıra Java'nın sızdığı açık olmayan bir durum olacaktır.

  • File.deleteOnExit() - her zaman ipi sızdırıyor, dize bir alt dize ise, sızıntı daha da kötüdür (alttaki char [] da sızdırılır)- Java 7'de alt dize de kopyalar char[], bu nedenle daha sonra geçerli değildir ; @ Daniel, oylamaya gerek yok.

En çok yönetilmeyen ipliklerin tehlikesini göstermek için ipliklere konsantre olacağım, salıncaka dokunmak bile istemiyorum.

  • Runtime.addShutdownHookKaldırmayın ... ve sonra removeShutdownHook ile bile başlatılmamış iş parçacıkları ile ilgili ThreadGroup sınıfındaki bir hata nedeniyle, ThreadGroup etkin bir şekilde sızıntı. JGroup'un GossipRouter'da kaçağı var.

  • A oluşturmak, ancak başlamak değil, Threadyukarıdakiyle aynı kategoriye girer.

  • Bir iplik devralır oluşturma ContextClassLoaderve AccessControlContextartı ThreadGroupve herhangi InheritedThreadLocalonca referanslar sınıf yükleyicisi ve tüm statik referanslar ve ja-ja tarafından yüklenen tüm sınıfları ile birlikte, potansiyel bir sızıntı vardır. Etki özellikle süper basit bir ThreadFactoryarayüze sahip olan tüm jucExecutor çerçevesi ile görülebilir , ancak çoğu geliştiricinin gizlenen tehlike hakkında hiçbir fikri yoktur. Ayrıca birçok kütüphane istek üzerine iş parçacığı başlatır (çok fazla sektör popüler kütüphanesi).

  • ThreadLocalönbelleğe; bunlar birçok durumda kötüdür. Eminim herkes ThreadLocal, kötü haber dayalı biraz basit önbellekleri gördü: iplik ClassLoader bağlamında beklenenden daha fazla devam ederse, bu saf güzel küçük bir sızıntı. Gerçekten gerekmedikçe ThreadLocal önbellekleri kullanmayın.

  • Arayan ThreadGroup.destroy()ThreadGroup hiçbir konuları kendisi vardır, ama yine de çocuk ThreadGroups tutar zaman. ThreadGroup'un üst öğesinden kaldırılmasını engelleyen kötü bir sızıntı, ancak tüm çocuklar numaralandırılamaz hale geliyor.

  • WeakHashMap ve değeri (in) kullanılması doğrudan anahtara başvurur. Bu bir yığın dökümü olmadan bulmak zor bir. Bu Weak/SoftReference, korunan nesneye sert bir referans tutabilecek tüm genişletilmiş durumlar için geçerlidir .

  • Kullanma java.net.URLHTTP (S) protokolü ile ve gelen kaynağı yüklenirken (!). Bu özeldir, KeepAliveCacheThreadGroup sisteminde geçerli iş parçacığının bağlam sınıf yükleyicisine sızan yeni bir iş parçacığı oluşturur. Canlı bir iş parçacığı olmadığında iş parçacığı ilk istek üzerine oluşturulur, böylece ya şanslı olabilirsiniz ya da sızıntı olabilir. Sızıntı Java 7'de zaten düzeltilmiştir ve iş parçacığı oluşturan kod, bağlam sınıfı yükleyiciyi düzgün bir şekilde kaldırır. Birkaç vaka daha var (ImageFetcher gibi, ayrıca sabit ) benzer iş parçacıkları oluşturma.

  • Yapıcıdan InflaterInputStreamgeçirmeyi new java.util.zip.Inflater()( PNGImageDecoderörneğin) kullanma ve end()şişiriciyi çağırmama . Eğer yapıcıyı sadece newşansınız olmadan geçirirseniz ... Ve evet, close()akış yapıldığında manuel olarak yapıcı parametresi olarak aktarılırsa, şişiriciyi kapatmaz. Sonlandırıcı tarafından serbest bırakılacağı için bu gerçek bir sızıntı değil ... gerekli gördüğü zaman. O ana kadar yerel belleği o kadar kötü yiyor ki, Linux oom_killer'in süreci cezasız bir şekilde öldürmesine neden olabilir. Ana sorun, Java'da sonlandırmanın çok güvenilmez olması ve G1'in 7.0.2'ye kadar daha kötü hale getirmesidir. Hikayenin ahlaki: mümkün olan en kısa sürede yerel kaynakları serbest bırakın; sonlandırıcı çok zayıf.

  • İle aynı durum java.util.zip.Deflater. Deflater Java'da belleğe aç olduğu için bu çok daha kötüdür, yani her zaman birkaç bit KB yerel bellek ayırmak için 15 bit (maks.) Ve 8 bellek seviyesi (9 maks.) Kullanır. Neyse ki, Deflateryaygın olarak kullanılmaz ve bence JDK yanlış kullanım içermez. end()Manuel olarak bir Deflaterveya oluşturursanız her zaman arayın Inflater. Son ikisinin en iyi kısmı: mevcut normal profil oluşturma araçlarıyla bulamazsınız.

(İstek üzerine karşılaştığım zaman kaybını biraz daha ekleyebilirim.)

İyi şanslar selametle; sızıntılar kötülük!


23
Creating but not starting a Thread...Yikes, bundan birkaç yüzyıl önce kötü bir şekilde ısırıldım! (Java 1.3)
Leonbloy

@leonbloy, iş parçacığı doğrudan iş parçacığı grubuna eklendiğinden daha da kötüydü, başlamamak çok zor sızıntı anlamına geliyordu. Sadece unstartedsayıyı artırmaz, ancak iplik grubunun yok edilmesini önler (daha az kötülük ama yine de bir sızıntı)
bestsss

Teşekkür ederim! " ThreadGroup.destroy()ThreadGroup kendi iş parçacığı yokken çağırmak ..." inanılmaz ince bir hata; Bunu saatlerdir kovaladım, yoldan çıktım, çünkü kontrol GUI'mdeki ipliği numaralandırmak hiçbir şey göstermedi, ancak iplik grubu ve muhtemelen en az bir çocuk grubu gitmeyecekti.
Lawrence Dol

1
@bestsss: Merak ediyorum, neden JVM kapanmasında çalıştığı göz önüne alındığında bir kapatma kancasını kaldırmak istersiniz?
Lawrence Dol

203

Buradaki örneklerin çoğu "çok karmaşık" tır. Bunlar uç davalardır. Bu örneklerle, programcı bir hata yaptı (eşittir / hashcode'u tanımlamayın gibi) veya JVM / JAVA'nın (statik ile sınıf yükü) bir köşe durumu tarafından ısırıldı. Bence bu, bir görüşmecinin istediği türden bir örnek ya da en yaygın durum değil.

Ancak bellek sızıntıları için gerçekten daha basit durumlar var. Çöp toplayıcı yalnızca artık referansta bulunulmayanları boşaltır. Java geliştiricileri olarak hafızayı önemsemiyoruz. Gerektiğinde tahsis ediyoruz ve otomatik olarak serbest bırakılıyoruz. İnce.

Ancak, uzun ömürlü herhangi bir başvurunun devlet paylaşma eğilimi vardır. Herhangi bir şey, statik, tek ton olabilir ... Genellikle önemsiz olmayan uygulamalar karmaşık nesneler grafik yapma eğilimindedir. Null değerine bir referans ayarlamayı unutmak veya daha sık olarak bir nesneyi bir koleksiyondan kaldırmayı unutmak bellek sızıntısı yapmak için yeterlidir.

Elbette her türlü dinleyici (UI dinleyicisi gibi), önbellek veya uzun ömürlü paylaşılan bir durum, doğru şekilde ele alınmadığında bellek sızıntısı üretme eğilimindedir. Anlaşılması gereken, bunun bir Java köşe durumu veya çöp toplayıcıyla ilgili bir sorun olmadığıdır. Bu bir tasarım problemidir. Uzun ömürlü bir nesneye bir dinleyici eklediğimizi düşünüyoruz, ancak artık gerekmediğinde dinleyiciyi kaldırmıyoruz. Nesneleri önbelleğe alıyoruz, ancak bunları önbellekten kaldırma stratejimiz yok.

Belki bir hesaplama için gerekli olan önceki durumu saklayan karmaşık bir grafiğimiz var. Fakat önceki devletin kendisi, önceki ve benzeri devletle bağlantılıdır.

SQL bağlantılarını veya dosyalarını kapatmamız gerektiği gibi. Null değerine uygun referanslar ayarlamamız ve öğeleri koleksiyondan kaldırmamız gerekiyor. Uygun önbellekleme stratejilerine sahip olmalıyız (maksimum bellek boyutu, öğe sayısı veya zamanlayıcılar). Bir dinleyicinin bilgilendirilmesine izin veren tüm nesneler hem addListener hem de removeListener yöntemi sağlamalıdır. Ve bu bildirimler artık kullanılmadığında, dinleyici listelerini temizlemelidir.

Bir bellek sızıntısı gerçekten de mümkündür ve mükemmel bir şekilde tahmin edilebilir. Özel dil özelliklerine veya köşe kasalarına gerek yoktur. Bellek sızıntıları ya bir şeyin eksik olduğunu, hatta tasarım problemlerinin bir göstergesidir.


24
Diğer cevaplarda insanların bu uç davaları ve püf noktalarını aradıklarını ve noktayı tamamen kaçırdıklarını komik buluyorum. Sadece bir daha asla kullanmayacak nesnelere işe yaramaz referansları tutan ve bu referansları asla kaldıran kod gösterebilirler; biri bu vakaların "gerçek" bellek sızıntısı olmadığını söyleyebilir, çünkü hala etraftaki bu nesnelere referanslar vardır, ancak program bu referansları bir daha asla kullanmazsa ve asla düşürmezse, tamamen " gerçek bellek sızıntısı ".
ehabkost

@Nicolas Bousquet: "Bellek sızıntısı gerçekten mümkün" Çok teşekkür ederim. +15 upvotes. Güzel. Burada, Go dili hakkında bir sorunun öncüsü olarak bu gerçeği belirtmek için bağırdım: stackoverflow.com/questions/4400311 Bu soruda hala olumsuz inişler var :(
SyntaxT3rr0r

Java ve .NET'teki GC, bir anlamda, diğer nesnelere referans tutan nesnelerin varsayım grafiğine dayanır, diğer nesnelere "önem veren" nesnelerin grafiğiyle aynıdır. Gerçekte, referans grafikte "şefkatli" ilişkileri temsil etmeyen kenarların mevcut olması mümkündür ve doğrudan veya dolaylı referans yolu (kullanılsa bile WeakReference) bir nesnenin başka bir nesnenin varlığını önemsemesi mümkündür. birinden diğerine. Bir nesne referansının yedek bir biti varsa, "hedefe önem veren" bir göstergeye sahip olmak yararlı olabilir ...
supercat

... ve PhantomReferencebir nesnenin ilgilenen kimseye sahip olmadığı tespit edildiğinde, sistemin (benzer yollarla ) bildirim sağlamasını sağlayın . WeakReferencebiraz yakın gelir, ancak kullanılmadan önce güçlü bir referansa dönüştürülmesi gerekir; güçlü referans varken bir GC döngüsü meydana gelirse, hedefin yararlı olduğu varsayılır.
supercat

Bence bu doğru cevap. Yıllar önce bir simülasyon yazdık. Her nasılsa, önceki durumu yanlışlıkla bir bellek sızıntısı yaratarak mevcut duruma bağladık. Son teslim tarihi nedeniyle bellek sızıntısını asla çözmedik, ancak bunu belgeleyerek “özellik” haline getirdik.
nalply

163

Cevap tamamen görüşmecinin ne istediğini düşündüğüne bağlıdır.

Uygulamada Java sızıntısı yapmak mümkün müdür? Tabii ki, ve diğer cevaplarda çok sayıda örnek var.

Ama sorulmuş olabilecek birden fazla meta soru var mı?

  • Teorik olarak "mükemmel" bir Java uygulaması sızıntılara karşı savunmasız mı?
  • Aday teori ve gerçeklik arasındaki farkı anlıyor mu?
  • Aday çöp toplamanın nasıl çalıştığını anlıyor mu?
  • Ya da çöp toplama işleminin ideal bir durumda nasıl çalışması gerekiyor?
  • Yerel arayüzler üzerinden diğer dilleri arayabileceklerini biliyorlar mı?
  • Diğer dillerde hafıza sızdırmayı biliyorlar mı?
  • Aday, bellek yönetiminin ne olduğunu ve Java'da sahnenin arkasında neler olduğunu biliyor mu?

Meta sorunuzu "Bu röportaj durumunda kullanabileceğim bir cevap nedir" olarak okuyorum. Ve bu yüzden, Java yerine röportaj becerilerine odaklanacağım. Bir röportajda bir sorunun cevabını bilmeme durumunu, Java sızıntısını nasıl yapacağınızı bilmeniz gerektiğinden daha fazla tekrarlayacağınıza inanıyorum. Umarım, bu yardımcı olacaktır.

Görüşme için geliştirebileceğiniz en önemli becerilerden biri, soruları aktif olarak dinlemeyi öğrenmek ve niyetlerini çıkarmak için görüşmeci ile çalışmaktır. Bu sadece sorularına istedikleri gibi cevap vermenizi sağlamakla kalmaz, aynı zamanda bazı önemli iletişim becerilerinizin olduğunu gösterir. Eşit yetenekli birçok geliştirici arasında bir seçim söz konusu olduğunda, her seferinde cevap vermeden önce dinleyen, düşünen ve anlayan birini işe alacağım.


22
Ne zaman bu soru sordum, ben oldukça basit bir cevap arıyorum - bir sıra büyümeye devam, nihayet yakın db vb, garip classloader / iş parçacığı ayrıntıları değil, onlar gc sizin için ne yapamaz ve yapamaz anlamak anlamına gelir. Görüşme yaptığınız işe bağlı, sanırım.
DaveC

Lütfen soruma bir göz atın, teşekkürler stackoverflow.com/questions/31108772/…
Daniel Newtown

130

Anlamadığınız aşağıdakiler, bir çok anlamsız örnektir JDBC . Veya JDBC yakın bir geliştirici beklediğini nasıl en az Connection, Statementve ResultSetonları iptal edebilir veya bunlara referansları kaybederek yerine uygulanması güvenmeden önce örneklerini finalize.

void doWork()
{
   try
   {
       Connection conn = ConnectionFactory.getConnection();
       PreparedStatement stmt = conn.preparedStatement("some query"); // executes a valid query
       ResultSet rs = stmt.executeQuery();
       while(rs.hasNext())
       {
          ... process the result set
       }
   }
   catch(SQLException sqlEx)
   {
       log(sqlEx);
   }
}

Yukarıdakilerle ilgili problem, Connectionnesnenin kapalı olmaması ve dolayısıyla çöp toplayıcının gelip ulaşılamadığını görene kadar fiziksel bağlantının açık kalacağıdır. GC finalizeyöntemi çağırır , ancak finalizeen azından uygulandığı şekilde uygulamayan JDBC sürücüleri vardır Connection.close. Sonuçta ortaya çıkan davranış, toplanamayan nesneler nedeniyle bellek geri kazanılırken, Connectionnesne ile ilişkili kaynakların (bellek dahil) geri alınamayacağıdır.

Connection'S finalizeyönteminin her şeyi temizlemediği böyle bir durumda , veritabanı sunucusu sonunda bağlantının canlı olmadığını anlayana kadar, veritabanı sunucusuna yapılan fiziksel bağlantının birkaç çöp toplama döngüsü sürdüğü görülebilir ) ve kapatılmalıdır.

JDBC sürücüsü uygulanacak olsa bile, finalizesonlandırma sırasında istisnaların atılması mümkündür. Sonuçta ortaya çıkan davranış, şimdi "hareketsiz" nesneyle ilişkili herhangi bir belleğin, finalizeyalnızca bir kez çağrılması garanti edildiği gibi geri alınmayacağıdır.

Yukarıdaki nesne sonlandırma sırasında istisnalarla karşılaşma senaryosu, bellek sızıntısı - nesne dirilişine yol açabilecek başka bir senaryo ile ilgilidir. Nesne dirilişi genellikle kasıtlı olarak başka bir nesneden sonlandırılmadan nesneye güçlü bir referans yaratarak yapılır. Nesnenin yeniden canlandırılması kötüye kullanıldığında, diğer bellek sızıntıları kaynaklarıyla birlikte bellek sızıntısına neden olur.

Tasarlayabileceğiniz daha birçok örnek var -

  • ListYalnızca listeye eklediğiniz ve listeden silmediğiniz bir örneği yönetme (artık ihtiyacınız olmayan öğelerden kurtulmanız gerekir) veya
  • SocketS veya Files'yi açma , ancak artık gerekmediklerinde kapatma ( Connectionsınıfla ilgili yukarıdaki örneğe benzer ).
  • Bir Java EE uygulamasını indirirken Singletons kaldırılmıyor. Görünüşe göre, singleton sınıfını yükleyen Classloader, sınıfa başvuruda bulunacak ve dolayısıyla singleton örneği asla toplanmayacaktır. Uygulamanın yeni bir örneği dağıtıldığında, genellikle yeni bir sınıf yükleyici oluşturulur ve singleton nedeniyle eski sınıf yükleyicisi var olmaya devam eder.

98
Genellikle bellek sınırlarına ulaşmadan önce maksimum açık bağlantı sınırına ulaşırsınız. Bana neden bildiğimi sorma ...
Hardwareguy

Oracle JDBC sürücüsü bunu yapmakla ünlüdür.
chotchki

@ SQLware Connection.closeTüm SQL çağrılarımın nihayet bloğuna girene kadar SQL veritabanlarının bağlantı sınırlarına çok fazla vurdum . Ekstra eğlence için veritabanına çok fazla çağrı önlemek için Java tarafında kilitler gerektiren bazı uzun süredir çalışan Oracle saklı yordamlar çağırdı.
Michael Shopsin

@Hardwareguy Bu ilginç, ancak tüm ortamlar için gerçek bağlantı sınırlarına ulaşılması gerçekten gerekli değil. Örneğin, weblogic uygulama sunucusu 11g'de konuşlanmış bir uygulama için büyük ölçüde bağlantı sızıntıları gördüm. Ancak sızan bağlantıları toplama seçeneği nedeniyle, veritabanı sızıntıları başlatılırken veritabanı bağlantıları kullanılabilir durumda kalmıştır. Tüm ortamlardan emin değilim.
Aseem Bansal

Deneyimlerime göre, bağlantıyı kapatsanız bile bir bellek sızıntısı yaşarsınız. Önce ResultSet ve PreparedStatement'ı kapatmanız gerekir. Bunu yapmaya başlayana kadar OutOfMemoryErrors nedeniyle saatlerce, hatta günlerce çalıştıktan sonra tekrar tekrar çöken bir sunucu vardı.
Bjørn Stenfeldt

119

Muhtemelen olası bir bellek sızıntısının en basit örneklerinden biri ve bundan nasıl kaçınılacağı, ArrayList.remove (int) 'nin uygulanmasıdır:

public E remove(int index) {
    RangeCheck(index);

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index + 1, elementData, index,
                numMoved);
    elementData[--size] = null; // (!) Let gc do its work

    return oldValue;
}

Bunu kendiniz uyguluyorsanız, artık kullanılmayan dizi öğesini ( elementData[--size] = null) temizlemeyi düşünür müsünüz ? Bu referans büyük bir nesneyi canlı tutabilir ...


5
Ve burada bellek sızıntısı nerede?
rds

28
@maniek: Bu kodun bir bellek sızıntısı olduğunu ima etmek istememiştim. Yanlışlıkla nesne tutmayı önlemek için bazen açık olmayan kodun gerekli olduğunu göstermek için alıntı yaptım.
meriton

RangeCheck (dizin) nedir; ?
Koray Tugay

6
Joshua Bloch, Etkili Java'da Yığınların basit bir uygulamasını gösteren bu örneği verdi. Çok iyi bir cevap.
kiralar

Ama unutulsa bile bu GERÇEK bir bellek sızıntısı olmazdı. Elemana Yansıma ile hala GÜVENLİ olarak erişilirdi, sadece Liste arayüzü aracılığıyla açık ve doğrudan erişilebilir olmaz, ancak nesne ve referans hala oradadır ve güvenli bir şekilde erişilebilir.
DGoiko

68

Artık ihtiyaç duymadığınız nesnelere göndermeler yaptığınızda bellek sızıntısı olur. Bellek sızıntılarının kendilerini Java'da nasıl gösterdiğine ve bu konuda neler yapabileceğinize ilişkin örnekler için Java programlarında bellek sızıntılarını işleme bölümüne bakın .


14
Bunun bir "sızıntı" olduğuna inanmıyorum. Bu bir hata ve programın ve dilin tasarımıyla. Bir sızıntı, herhangi bir referans olmadan etrafta asılı duran bir nesne olabilir.
user541686

29
@Mehrdad: Bu, tüm diller için tam olarak geçerli olmayan tek bir dar tanım. Herhangi bir bellek sızıntısının, programın kötü tasarımının neden olduğu bir hata olduğunu iddia ediyorum .
Kertenkele Bill

9
@Mehrdad: ...then the question of "how do you create a memory leak in X?" becomes meaningless, since it's possible in any language. Bu sonucu nasıl çizdiğinizi anlamıyorum. Herhangi bir tanımla Java'da bellek sızıntısı oluşturmanın daha az yolu vardır . Kesinlikle geçerli bir soru.
Kertenkele Bill

7
@ 31eee384: Programınız nesneleri asla kullanamayacağı bir bellekte tutarsa, teknik olarak bellek sızıntı yapar. Daha büyük sorunlarınız olması bunu gerçekten değiştirmez.
Kertenkele Bill

8
@ 31eee384: Eğer olmayacak bir gerçeği biliyorsanız, olamaz. Program, yazıldığı gibi, verilere asla erişmeyecektir.
Kertenkele Bill

51

İle bellek sızıntısı yapabilirsiniz Sun.misc.Unsafe sınıfıyla . Aslında bu hizmet sınıfı farklı standart sınıflarda (örneğin java.nio sınıflarında) kullanılır. Bu sınıfın örneğini doğrudan oluşturamazsınız , ancak bunu yapmak için yansımayı kullanabilirsiniz .

Eclipse IDE'de kod derlenmiyor - komutu kullanarak derleyin javac derlenmiyor - (derleme sırasında uyarı alırsınız)

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import sun.misc.Unsafe;


public class TestUnsafe {

    public static void main(String[] args) throws Exception{
        Class unsafeClass = Class.forName("sun.misc.Unsafe");
        Field f = unsafeClass.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        System.out.print("4..3..2..1...");
        try
        {
            for(;;)
                unsafe.allocateMemory(1024*1024);
        } catch(Error e) {
            System.out.println("Boom :)");
            e.printStackTrace();
        }
    }

}

1
Tahsis edilen bellek çöp toplayıcı için görünmez
stemm

4
Ayrılan bellek de Java'ya ait değil.
bestsss

Bu güneş / oracle jvm'ye özgü mü? Örneğin, bu IBM üzerinde çalışacak mı?
Berlin Brown

2
Bellek kesinlikle "Java'ya aittir", en azından i) başkası tarafından kullanılamaması, ii) Java uygulaması çıktığında sisteme iade edilecektir. JVM'nin hemen dışında.
Michael Anderson

3
Bu tutulma (en azından son sürümlerde) oluşturacaktır, ancak derleyici ayarlarını değiştirmeniz gerekir: Pencere> Tercihler> Java> Derleyici> Hatalar / Uyarı> Kullanımdan kaldırıldı ve kısıtlanmış API, Yasak başvuru (erişim kuralları) olarak "Uyarı" ".
Michael Anderson

43

Cevabımı buradan kopyalayabilirim: Java'da bellek sızıntısına neden olmanın en kolay yolu?

"Bilgisayar bilimlerinde bellek sızıntısı (veya bu bağlamda sızıntı), bir bilgisayar programı bellek tükettiğinde ancak işletim sistemine geri bırakamadığında oluşur." (Vikipedi)

Kolay cevap: Yapamazsınız. Java otomatik bellek yönetimi yapar ve sizin için gerekli olmayan kaynakları serbest bırakır. Bunun olmasını engelleyemezsin. Her zaman kaynakları serbest bırakabilecektir. Manuel bellek yönetimi olan programlarda bu farklıdır. Malloc () kullanarak C'de biraz bellek alamazsınız. Belleği boşaltmak için, malloc'un döndürdüğü işaretçiyi ve üzerinde free () öğesini çağırmanız gerekir. Ancak artık işaretçiye sahip değilseniz (üzerine yazılmış veya kullanım ömrü aşılmışsa), bu maalesef bu belleği boşaltmaktan acizsiniz ve dolayısıyla bir bellek sızıntısına sahip olursunuz.

Şimdiye kadar olan diğer tüm cevaplar benim tanımımda gerçekten bellek sızıntısı değil. Hepsi hafızayı anlamsız şeylerle çok hızlı doldurmayı amaçlıyor. Ama herhangi bir zamanda hala yarattığınız nesneleri dereference ve böylece bellek serbest -> NO LEAK. Acconrad'ın cevabı oldukça yakın geliyor ama itiraf etmeliyim çünkü çözümü etkili bir şekilde çöp toplayıcıyı sonsuz bir döngüde zorlayarak "çökertmek".

Uzun cevap şudur: JNI'yi kullanarak manuel bellek yönetimi ve dolayısıyla bellek sızıntıları olan bir Java kütüphanesi yazarak bellek sızıntısı elde edebilirsiniz. Bu kütüphaneyi çağırırsanız, java işleminiz bellek sızdırır. Veya JVM'de hatalar olabilir, böylece JVM belleği kaybeder. JVM'de muhtemelen hatalar var, çöp toplama o kadar da önemsiz olduğu için bazı bilinenler bile olabilir, ancak yine de bir hata. Tasarım gereği bu mümkün değildir. Böyle bir hatadan etkilenen bazı java kodlarını isteyebilirsiniz. Maalesef birini bilmiyorum ve zaten bir sonraki Java sürümünde artık bir hata olmayabilir.


12
Bu, bellek sızıntılarının son derece sınırlı (ve çok yararlı olmayan) bir tanımıdır. Pratik amaçlar için anlamlı olan tek tanım "bellek sızıntısı, programın tuttuğu verilerden sonra artık gerekli olmayan belleği tutmaya devam ettiği herhangi bir durumdur."
Mason Wheeler

1
Bahsedilen acconrad cevabı silindi mi?
Tomáš Zato - Monica

1
@ TomášZato: Hayır, değil. Şimdi referans vermek için yukarıdaki referansı çevirdim, böylece kolayca bulabilirsiniz.
yankee

Nesne dirilişi nedir? Bir yıkıcı kaç kez çağrılır? Bu sorular bu cevabı nasıl çürütür?
otistik

1
GC'ye rağmen Java içinde bir bellek sızıntısı oluşturabildiğinizden ve yine de yukarıdaki tanımı yerine getirdiğinizden emin olun. Harici bir erişime karşı korunan yalnızca ek bir veri yapısına sahip olun, böylece başka hiçbir kod onu kaldırmaz - program, kodu içermediği için belleği serbest bırakamaz.
toolforger

39

İşte http://wiki.eclipse.org/Performance_Bloopers#String.substring.28.29 aracılığıyla basit / uğursuz bir örnek .

public class StringLeaker
{
    private final String muchSmallerString;

    public StringLeaker()
    {
        // Imagine the whole Declaration of Independence here
        String veryLongString = "We hold these truths to be self-evident...";

        // The substring here maintains a reference to the internal char[]
        // representation of the original string.
        this.muchSmallerString = veryLongString.substring(0, 1);
    }
}

Alt dize orijinalin çok daha uzun bir dizesinin iç temsilini ifade ettiğinden, orijinal bellekte kalır. Bu nedenle, oyunda bir StringLeaker'ınız olduğu sürece, sadece tek karakterli bir dize tuttuğunuzu düşünebiliyor olsanız bile, orijinal dize de sahip olursunuz.

Orijinal dizeye istenmeyen bir başvuru depolamaktan kaçınmanın yolu şöyle bir şey yapmaktır:

...
this.muchSmallerString = new String(veryLongString.substring(0, 1));
...

Ek kötülük .intern()için alt dizeyi de kullanabilirsiniz:

...
this.muchSmallerString = veryLongString.substring(0, 1).intern();
...

Bunu yaptığınızda, StringLeaker örneği atıldıktan sonra bile hem orijinal uzun dize hem de türetilen alt dize bellekte kalır.


4
Bunun bir bellek sızıntısı, çağrı değildir haddi zatında . Serbest muchSmallerStringbırakıldığında ( StringLeakernesne yok edildiğinden), uzun dize de serbest bırakılır. Bellek sızıntısı dediğim şey, bu JVM örneğinde asla serbest bırakılamayan bellektir. Ancak, hafızayı boşaltmak için nasıl kendini göstermiştir: this.muchSmallerString=new String(this.muchSmallerString). Gerçek bir bellek sızıntısı ile yapabileceğiniz hiçbir şey yoktur.
rds

2
@rds, bu adil bir nokta. Olmayan internvaka bir daha bir "hafıza sürpriz" olabilir "bellek sızıntısı." .intern()Bununla birlikte, alt dizenin kullanılması, kesinlikle daha uzun dizgeye yapılan başvurunun korunduğu ve serbest bırakılamadığı bir durum oluşturur.
Jon Chambers

15
Substring () yöntemi, java7'de yeni bir Dize oluşturur (yeni bir davranıştır)
anstarovoyt

Subring () 'i kendiniz bile yapmanız gerekmez: Büyük bir girdinin küçük bir kısmını eşleştirmek için bir normal ifade eşleştirici kullanın ve "ayıklanan" dizeyi uzun süre taşıyın. Devasa girdi Java 6'ya kadar yaşıyor.
Bananeweizen

37

GUI kodunda bunun yaygın bir örneği, bir widget / bileşen oluşturma ve bazı statik / uygulama kapsamındaki nesnelere dinleyici ekleme ve ardından widget yok edildiğinde dinleyiciyi kaldırma işlemidir. Sadece bir bellek sızıntısı değil, aynı zamanda olayları ne dinlerseniz dinleyin, tüm eski dinleyicileriniz de çağrılır.


1
Android platformu , bir Görünümün statik alanında bir Bitmap önbelleğe alınarak oluşturulan bir bellek sızıntısına örnek verir .
rds

36

Herhangi bir sunucu uygulamasında (Tomcat, Jetty, Glassfish, ne olursa olsun) çalışan herhangi bir web uygulamasını alın. Uygulamayı arka arkaya 10 veya 20 kez yeniden konuşlandırın (sunucunun otomatik devreye alma dizinindeki WAR öğesine dokunmanız yeterli olabilir.

Kimse bunu gerçekten test etmedikçe, birkaç yeniden dağıtımdan sonra bir OutOfMemoryError alma olasılığınız yüksektir, çünkü uygulama kendi kendine temizlemeye özen göstermedi. Bu testle sunucunuzda bir hata bile bulabilirsiniz.

Sorun şu ki, kabın ömrü uygulamanızın ömründen daha uzun. Kapsayıcıya uygulamanızın nesnelerine veya sınıflarına ilişkin tüm referansların çöp toplanabileceğinden emin olmalısınız.

Web uygulamanızın kaldırılmasından sonra sadece bir referans varsa, karşılık gelen sınıf yükleyici ve sonuç olarak web uygulamanızın tüm sınıfları çöp toplanamaz.

Uygulamanız tarafından başlatılan iş parçacıkları, ThreadLocal değişkenleri, günlük ekleyicileri, sınıf yükleyici sızıntılarına neden olan olağan şüphelilerden bazılarıdır.


1
Bunun nedeni bellek sızıntısı değil, sınıf yükleyicinin önceki sınıf kümesini kaldırmamasıdır. Bu nedenle, sunucuyu yeniden başlatmadan bir uygulama sunucusunu yeniden konuşlandırmanız önerilmez (fiziksel makine değil, uygulama sunucusu). Aynı sorunu WebSphere ile de gördüm.
Sven

35

Belki JNI aracılığıyla harici yerel kod kullanarak?

Saf Java ile neredeyse imkansız.

Ancak bu, artık belleğe erişemediğinizde "standart" bir bellek sızıntısıyla ilgilidir, ancak yine de uygulamaya aittir. Bunun yerine kullanılmayan nesnelere başvuruları saklayabilir veya akışları daha sonra kapatmadan açabilirsiniz.


22
Bu "bellek sızıntısı" tanımına bağlıdır. "Tutulan ancak artık gerekli olmayan bellek" ise, Java ile yapmak kolaydır. Eğer "tahsis edilmiş ancak kod tarafından erişilemeyen bellek" ise, o zaman biraz zorlaşır.
Joachim Sauer

@Joachim Sauer - İkinci tip demek istedim. İlk yapmak oldukça kolay :)
Rogach

6
"Saf java ile neredeyse imkansız." Deneyimlerim başka bir şey, özellikle de buradaki tuzaklardan haberdar olmayan insanlar tarafından önbelleklerin uygulanması söz konusu olduğunda.
Fabian Barney

4
@ Roach: Temelde +10 000 temsilcisi olan kişilerin çeşitli cevapları üzerinde +400 upvotes var, her iki durumda da Joachim Sauer'in çok mümkün olduğunu belirtti. Yani "neredeyse imkansız" nız hiçbir anlam ifade etmiyor.
SyntaxT3rr0r

32

Bir kez PermGen ve XML ayrıştırma ile ilgili güzel bir "bellek sızıntısı" oldu. Daha hızlı karşılaştırma yapmak için kullandığımız XML ayrıştırıcısı (hangisinin olduğunu hatırlayamıyorum) etiket adlarında bir String.intern () yaptı. Müşterilerimizden biri, veri değerlerini XML özelliklerinde veya metinde değil, tagnames olarak depolamak için harika bir fikre sahipti, bu yüzden şöyle bir belgemiz vardı:

<data>
   <1>bla</1>
   <2>foo</>
   ...
</data>

Aslında, benzersiz değil ve günde 10-15 milyon oranında gelen rakamlar değil daha uzun metin kimlikleri (yaklaşık 20 karakter) kullandılar. Bu günde 200 MB çöp yapar, bir daha asla ihtiyaç duyulmaz ve asla GCed olmaz (PermGen'de olduğu için). 512 MB'ye ayarlanmış bir permenimiz vardı, bu yüzden bellek yetersiz özel durumun (OOME) gelmesi yaklaşık iki gün sürdü ...


4
Sadece örnek kod nitpick için: Bence sayılar (veya sayılar ile başlayan dizeleri) XML öğe adları olarak izin verilmez.
Paŭlo Ebermann

Dize stajyerinin yığınta gerçekleştiği JDK 7+ için artık geçerli olmadığını unutmayın. Ayrıntılı bir yazı için bu makaleye bakın: java-performance.info/string-intern-in-java-6-7-8
jmiserez

Yani, String yerine StringBuffer kullanarak bu sorunu çözeceğini düşünüyorum? alışkanlık?
anubhs

24

Bellek sızıntısı nedir:

  • Bir hata veya kötü tasarımdan kaynaklanır.
  • Bu bir bellek kaybı.
  • Zamanla kötüleşir.
  • Çöp toplayıcı temizleyemez.

Tipik örnek:

Nesnelerin önbelleği, işleri karıştırmak için iyi bir başlangıç ​​noktasıdır.

private static final Map<String, Info> myCache = new HashMap<>();

public void getInfo(String key)
{
    // uses cache
    Info info = myCache.get(key);
    if (info != null) return info;

    // if it's not in cache, then fetch it from the database
    info = Database.fetch(key);
    if (info == null) return null;

    // and store it in the cache
    myCache.put(key, info);
    return info;
}

Önbelleğiniz büyür ve büyür. Ve çok geçmeden tüm veritabanı belleğe çekilir. Daha iyi bir tasarım LRUMap kullanır (Sadece son kullanılan nesneleri önbellekte tutar).

Elbette, işleri çok daha karmaşık hale getirebilirsiniz:

  • ThreadLocal yapıları kullanarak .
  • daha karmaşık referans ağaçları ekleyerek .
  • veya 3. taraf kütüphanelerin neden olduğu sızıntılar .

Sık sık ne olur:

Bu Info nesnesinin başka nesnelere başvuruları varsa, yine başka nesnelere başvuruları vardır. Bir şekilde, bunun bir tür bellek sızıntısı olduğunu da düşünebilirsiniz (kötü tasarımın neden olduğu).


22

Kimsenin iç sınıf örneklerini kullanmamasının ilginç olduğunu düşündüm. Dahili bir sınıfınız varsa; doğal olarak içeren sınıfa bir referans tutar. Tabii ki teknik olarak bir bellek sızıntısı değildir çünkü Java sonunda temizleyecektir; ancak bu, sınıfların beklenenden daha uzun süre asılı kalmasına neden olabilir.

public class Example1 {
  public Example2 getNewExample2() {
    return this.new Example2();
  }
  public class Example2 {
    public Example2() {}
  }
}

Şimdi Örnek1'i çağırır ve Örnek1'i atarak bir Örnek2 alırsanız, kendiliğinden bir Örnek1 nesnesine bağlantınız olur.

public class Referencer {
  public static Example2 GetAnExample2() {
    Example1 ex = new Example1();
    return ex.getNewExample2();
  }

  public static void main(String[] args) {
    Example2 ex = Referencer.GetAnExample2();
    // As long as ex is reachable; Example1 will always remain in memory.
  }
}

Ayrıca, belirli bir süreden daha uzun bir süredir var olan bir değişkeniniz varsa; Java her zaman var olacağını ve artık kodda ulaşılamazsa asla temizlemeye çalışmadığını varsayar. Ancak bu tamamen doğrulanmamıştır.


2
iç sınıflar nadiren bir konudur. Bunlar basit bir durumdur ve tespit edilmesi çok kolaydır. Söylenti de sadece bir söylenti.
bestsss

2
"Söylenti" kulağa GC'nin nasıl çalıştığı hakkında yarı-okunmuş gibi geliyor. Uzun ömürlü ama şimdi ulaşılamayan nesneler gerçekten bir süre yapışabilir ve bir süre yer kaplayabilir, çünkü JVM onları genç nesillerden tanıttı, böylece her geçişi kontrol etmeyi bırakabilirdi. Tasarımla, "5000 geçici iplerimi temizle" pasolarından kaçacaklar. Ama ölümsüz değiller. Hala toplama için uygundurlar ve VM RAM için bağlanırsa, sonunda tam bir GC taraması yapar ve bu belleği yeniden siler.
cHao

22

Son zamanlarda log4j neden bir bellek sızıntı durumu ile karşılaştı.

Log4j, araya eklenmiş günlük çıktısını farklı kaynaklardan ayıran bir araç olan İç İçe Diyagnostik Bağlam (NDC) olarak adlandırılan bu mekanizmaya sahiptir . NDC'nin çalıştığı ayrıntı düzeyi iş parçacıklarıdır, bu nedenle günlük çıktılarını farklı iş parçacıklarından ayrı olarak ayırır.

İş parçacığına özgü etiketleri saklamak için, log4j'nin NDC sınıfı, Thread nesnesinin kendisi tarafından (thread id ifadesinin aksine) anahtarlanan bir Hashtable kullanır ve böylece NDC etiketi, thread'da kalan tüm nesneleri hafızada kalana kadar nesne de hafızada kalır. Web uygulamamızda, günlükleri tek bir istekten ayrı olarak ayırmak için bir istek kimliğine sahip çıkışları etiketlemek için NDC'yi kullanırız. NDC etiketini bir iş parçacığıyla ilişkilendiren kapsayıcı, bir istekten yanıtı döndürürken etiketi de kaldırır. Sorun, bir isteğin işlenmesi sırasında, aşağıdaki kod gibi bir alt iş parçacığının doğması sırasında ortaya çıktı:

pubclic class RequestProcessor {
    private static final Logger logger = Logger.getLogger(RequestProcessor.class);
    public void doSomething()  {
        ....
        final List<String> hugeList = new ArrayList<String>(10000);
        new Thread() {
           public void run() {
               logger.info("Child thread spawned")
               for(String s:hugeList) {
                   ....
               }
           }
        }.start();
    }
}    

Böylece bir NDC bağlamı ortaya çıkan satır içi iş parçacığıyla ilişkilendirildi. Bu NDC bağlamının anahtarı olan iş parçacığı nesnesi, büyükList nesnesinin asılı olduğu satır içi iş parçacığıdır. Böylece, iş parçacığı yaptığı işi bitirdikten sonra bile, kocaman listeye yapılan başvuru Hastable NDC bağlamı tarafından canlı tutuldu ve böylece bellek sızıntısına neden oldu.


Bu berbat. Bir dosyaya giriş yaparken SIFIR belleği ayıran bu günlük kütüphanesini kontrol etmelisiniz: mentalog.soliveirajr.com
TraderJoeChicago 20:11

+1 slf4j / logback'te (aynı yazarın halef ürünü) MDC ile benzer bir sorun olup olmadığını hazırlıksız biliyor musunuz? Kaynakta derin bir dalış yapmak üzereyim ama önce kontrol etmek istedim. Her iki durumda da, bunu gönderdiğiniz için teşekkürler.
sparc_spread

20

Görüşmeci muhtemelen aşağıdaki kod gibi dairesel bir referans arıyordu. Ancak bu oldukça belirsiz bir soru, bu yüzden JVM bellek yönetimi anlayışınızı göstermek için birincil bir fırsat.

class A {
    B bRef;
}

class B {
    A aRef;
}

public class Main {
    public static void main(String args[]) {
        A myA = new A();
        B myB = new B();
        myA.bRef = myB;
        myB.aRef = myA;
        myA=null;
        myB=null;
        /* at this point, there is no access to the myA and myB objects, */
        /* even though both objects still have active references. */
    } /* main */
}

Daha sonra referans sayımıyla yukarıdaki kodun bellek sızdırdığını açıklayabilirsiniz. Ancak modern JVM'lerin çoğunda artık referans sayımı kullanılmıyor, çoğu bu hafızayı toplayacak bir süpürme çöp toplayıcısı kullanıyor.

Daha sonra, aşağıdaki gibi temel kaynağı olan bir Nesne oluşturmayı açıklayabilirsiniz:

public class Main {
    public static void main(String args[]) {
        Socket s = new Socket(InetAddress.getByName("google.com"),80);
        s=null;
        /* at this point, because you didn't close the socket properly, */
        /* you have a leak of a native descriptor, which uses memory. */
    }
}

Daha sonra bunun teknik olarak bir bellek sızıntısı olduğunu açıklayabilirsiniz, ancak gerçekten de sızıntı, JVM'deki Java kodunuz tarafından serbest bırakılmayan yerel kaynakları tahsis eden yerel koddan kaynaklanır.

Günün sonunda, modern bir JVM ile, yerel bir kaynağı JVM'nin farkındalığının normal kapsamı dışında ayıran bazı Java kodları yazmanız gerekir.


19

Herkes her zaman yerel kod yolunu unutur. İşte bir sızıntı için basit bir formül:

  1. Yerel yöntemi bildirin.
  2. Yerel yöntemde arayın malloc. Arama free.
  3. Yerel yöntemi çağırın.

Yerel koddaki bellek ayırmalarının JVM yığınından geldiğini unutmayın.


1
Gerçek hikayeye dayalıdır.
Reg

18

Statik bir Harita oluşturun ve buna sert referanslar eklemeye devam edin. Bunlar asla GC'd olmayacak.

public class Leaker {
    private static final Map<String, Object> CACHE = new HashMap<String, Object>();

    // Keep adding until failure.
    public static void addToCache(String key, Object value) { Leaker.CACHE.put(key, value); }
}

87
Bu nasıl bir sızıntı? Tam olarak yapmasını istediğin şeyi yapıyor. Bu bir sızıntıysa, nesneleri herhangi bir yerde oluşturmak ve saklamak bir sızıntıdır.
Şubat

3
@Falmarri'ye katılıyorum. Orada bir sızıntı görmüyorum, sadece nesneler yaratıyorsunuz. Kesinlikle 'removeFromCache' adı verilen başka bir yöntemle ayırdığınız belleği 'geri alabilirsiniz'. Sızıntı, belleği geri alamayacağınız zamandır.
Kyle

3
Demek istediğim, nesne oluşturmaya devam eden, belki de onları bir önbelleğe koyan birinin, dikkatli olmazlarsa bir OOM hatasıyla sonuçlanabilir.
duffymo

8
@duffymo: Ama sorunun sorusu tam olarak bu değildi. Tüm hafızanızı kullanmakla hiçbir ilgisi yok.
Falmarri

3
Kesinlikle geçersiz. Bir Harita koleksiyonunda sadece bir grup nesne topluyorsunuz. Referansları Harita onları tuttuğu için saklanacaktır.
gyorgyabraham

16

Sınıfın sonlandırma yönteminde bir sınıfın yeni bir örneğini oluşturarak hareketli bir bellek sızıntısı oluşturabilirsiniz. Sonlandırıcı birden fazla örnek oluşturuyorsa bonus puanlar. Aşağıda, yığın boyutunuza bağlı olarak tüm yığını birkaç saniye ile birkaç dakika arasında sızdıran basit bir program var:

class Leakee {
    public void check() {
        if (depth > 2) {
            Leaker.done();
        }
    }
    private int depth;
    public Leakee(int d) {
        depth = d;
    }
    protected void finalize() {
        new Leakee(depth + 1).check();
        new Leakee(depth + 1).check();
    }
}

public class Leaker {
    private static boolean makeMore = true;
    public static void done() {
        makeMore = false;
    }
    public static void main(String[] args) throws InterruptedException {
        // make a bunch of them until the garbage collector gets active
        while (makeMore) {
            new Leakee(0).check();
        }
        // sit back and watch the finalizers chew through memory
        while (true) {
            Thread.sleep(1000);
            System.out.println("memory=" +
                    Runtime.getRuntime().freeMemory() + " / " +
                    Runtime.getRuntime().totalMemory());
        }
    }
}

15

Kimsenin bunu henüz söylediğini sanmıyorum: finalize () yöntemini geçersiz kılarak bir nesneyi diriltebilirsiniz, böylece finalize () bunun bir referansını saklar. Çöp toplayıcı nesnede yalnızca bir kez çağrılır, bundan sonra nesne asla yok olmaz.


10
Bu doğru değil. finalize()çağrılmaz, ancak daha fazla referans olmayacak şekilde nesne toplanır. Çöp toplayıcı da 'çağrılmaz'.
bestsss

1
Bu cevap yanıltıcıdır, finalize()yöntem JVM tarafından yalnızca bir kez çağrılabilir, ancak bu, nesne yeniden dirilir ve sonra tekrar başvurulursa yeniden toplanamayacağı anlamına gelmez. finalize()Yöntemde kaynak kapatma kodu varsa, bu kod yeniden çalıştırılmaz, bu bellek sızıntısına neden olabilir.
Tom Cammann

15

Son zamanlarda daha ince bir kaynak sızıntısıyla karşılaştım. Sınıf yükleyicisinin getResourceAsStream aracılığıyla kaynakları açıyoruz ve giriş akışı tanıtıcıları kapatılmadı.

Uhm, diyebilirsin, ne salakça.

Bunu ilginç kılan şey: Bu şekilde, JVM'nin yığınından ziyade altta yatan sürecin yığın hafızasını sızdırabilirsiniz.

İhtiyacınız olan tek şey, içinde Java kodundan başvurulacak bir dosya bulunan bir kavanoz dosyasıdır. Kavanoz dosyası büyüdükçe, daha hızlı bellek ayrılır.

Aşağıdaki sınıfla kolayca böyle bir kavanoz oluşturabilirsiniz:

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class BigJarCreator {
    public static void main(String[] args) throws IOException {
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("big.jar")));
        zos.putNextEntry(new ZipEntry("resource.txt"));
        zos.write("not too much in here".getBytes());
        zos.closeEntry();
        zos.putNextEntry(new ZipEntry("largeFile.out"));
        for (int i=0 ; i<10000000 ; i++) {
            zos.write((int) (Math.round(Math.random()*100)+20));
        }
        zos.closeEntry();
        zos.close();
    }
}

Sadece BigJarCreator.java adlı bir dosyaya yapıştırın, derleyin ve komut satırından çalıştırın:

javac BigJarCreator.java
java -cp . BigJarCreator

Et voilà: geçerli çalışma dizininizde içinde iki dosya bulunan bir kavanoz arşivi bulacaksınız.

İkinci bir sınıf oluşturalım:

public class MemLeak {
    public static void main(String[] args) throws InterruptedException {
        int ITERATIONS=100000;
        for (int i=0 ; i<ITERATIONS ; i++) {
            MemLeak.class.getClassLoader().getResourceAsStream("resource.txt");
        }
        System.out.println("finished creation of streams, now waiting to be killed");

        Thread.sleep(Long.MAX_VALUE);
    }

}

Bu sınıf temelde hiçbir şey yapmaz, ancak referanssız InputStream nesneleri oluşturur. Bu nesneler hemen çöp toplanacak ve bu nedenle yığın boyutuna katkıda bulunmayacaktır. Örneğimiz için mevcut bir kaynağı bir jar dosyasından yüklemek önemlidir ve burada boyut önemlidir.

Şüpheniz varsa, yukarıdaki sınıfı derlemeye ve başlatmaya çalışın, ancak iyi bir yığın boyutu (2 MB) seçtiğinizden emin olun:

javac MemLeak.java
java -Xmx2m -classpath .:big.jar MemLeak

Burada bir OOM hatasıyla karşılaşmayacaksınız, hiçbir referans tutulmadığından, yukarıdaki örnekte ne kadar büyük ITERATIONS seçmiş olursanız olun uygulama çalışmaya devam edecektir. Uygulama wait komutuna ulaşmadıkça, işleminizin bellek tüketimi (üstte (RES / RSS) veya işlem gezgininde görünür) artar. Yukarıdaki kurulumda, yaklaşık 150 MB bellek ayıracaktır.

Uygulamanın güvenli oynatılmasını istiyorsanız, giriş akışını oluşturulduğu yerden kapatın:

MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close();

ve işleminiz yineleme sayısından bağımsız olarak 35 MB'ı aşmayacaktır.

Oldukça basit ve şaşırtıcı.


14

Bir çok insanın önerdiği gibi, Kaynak Kaçaklarına neden olması oldukça kolaydır - JDBC örnekleri gibi. Gerçek Bellek sızıntıları biraz daha zordur - özellikle de sizin için JVM'nin kırık parçalarına güvenmiyorsanız ...

Çok büyük bir alan kaplayan ve daha sonra bunlara erişemeyen nesneler oluşturma fikirleri de gerçek bellek sızıntıları değildir. Hiçbir şey ona erişemezse, o zaman çöp toplanır ve bir şey erişebilirse o zaman bir sızıntı değildir ...

Eskiden çalışmanın bir yolu - ve hala işe yarayıp yaramadığını bilmiyorum - üç derin dairesel bir zincire sahip olmak. A nesnesindeki gibi B nesnesine bir referansı vardır, B nesnesinin C nesnesine bir referansı vardır ve C nesnesinin A nesnesine bir referansı vardır. GC, iki derin zincirin - A <--> B - A ve B'ye başka bir şey tarafından erişilemiyorsa, ancak üç yönlü zinciri kaldıramazsa, güvenli bir şekilde toplanabilir ...


7
Bir süredir böyle değil. Modern GC'ler dairesel referansların nasıl ele alınacağını bilir.
assylias

13

Potansiyel olarak büyük bellek sızıntıları oluşturmanın başka bir yolu Map.Entry<K,V>, a TreeMap.

Bunun neden sadece TreeMaps için geçerli olduğunu değerlendirmek zordur , ancak uygulamaya bakarak bunun nedeni şu olabilir: bir TreeMap.Entrymağaza kardeşlerine atıfta bulunur, bu nedenle a TreeMaptoplanmaya hazırsa, ancak diğer bazı sınıflar onun Map.Entryardından, tüm Harita hafızada tutulur.


Gerçek hayat senaryosu:

Büyük bir TreeMapveri yapısı döndüren bir db sorgusu olduğunu düşünün . İnsanlar genellikle TreeMapöğe ekleme sırası korunurken s kullanırlar .

public static Map<String, Integer> pseudoQueryDatabase();

Sorgu birçok kez çağrıldıysa ve her sorgu için (her biri Mapgeri döndüğünde) bir Entryyere kaydederseniz, bellek sürekli olarak büyümeye devam eder.

Aşağıdaki sarmalayıcı sınıfını düşünün:

class EntryHolder {
    Map.Entry<String, Integer> entry;

    EntryHolder(Map.Entry<String, Integer> entry) {
        this.entry = entry;
    }
}

Uygulama:

public class LeakTest {

    private final List<EntryHolder> holdersCache = new ArrayList<>();
    private static final int MAP_SIZE = 100_000;

    public void run() {
        // create 500 entries each holding a reference to an Entry of a TreeMap
        IntStream.range(0, 500).forEach(value -> {
            // create map
            final Map<String, Integer> map = pseudoQueryDatabase();

            final int index = new Random().nextInt(MAP_SIZE);

            // get random entry from map
            for (Map.Entry<String, Integer> entry : map.entrySet()) {
                if (entry.getValue().equals(index)) {
                    holdersCache.add(new EntryHolder(entry));
                    break;
                }
            }
            // to observe behavior in visualvm
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

    }

    public static Map<String, Integer> pseudoQueryDatabase() {
        final Map<String, Integer> map = new TreeMap<>();
        IntStream.range(0, MAP_SIZE).forEach(i -> map.put(String.valueOf(i), i));
        return map;
    }

    public static void main(String[] args) throws Exception {
        new LeakTest().run();
    }
}

Her pseudoQueryDatabase()çağrıdan sonra , mapörnekler toplanmaya hazır olmalıdır, ancak en azından biri Entrybaşka bir yerde saklandığından gerçekleşmez.

jvmAyarlarınıza bağlı olarak , uygulama nedeniyle erken aşamada çökebilir OutOfMemoryError.

Bu visualvmgrafikten belleğin nasıl büyüdüğünü görebilirsiniz.

Bellek dökümü - TreeMap

Karma veri yapısı ( HashMap) için de aynı şey olmaz .

Bu, a HashMap.

Bellek dökümü - HashMap

Çözüm? Kaydetmek yerine anahtarı / değeri (muhtemelen zaten yaptığınız gibi) doğrudan kaydedin Map.Entry.


Burada daha kapsamlı bir karşılaştırma ölçütü yazdım .


11

Konular sonlanıncaya kadar toplanmaz. Çöp toplamanın kökleri olarak hizmet ederler . Bunlar, sadece onları unutarak veya onlara atıfta bulunarak geri kazanılmayacak birkaç nesneden biridir.

Dikkate alın: bir iş parçacığı sonlandırmak için temel desen iş parçacığı tarafından görülen bazı koşul değişkeni ayarlamaktır. İş parçacığı, değişkeni periyodik olarak kontrol edebilir ve sonlandırmak için bir sinyal olarak kullanabilir. Değişken bildirilmezse volatile, değişkende yapılan değişiklik iş parçacığı tarafından görülmeyebilir, bu nedenle sonlandırmayı bilemez. Ya da bazı iş parçacıklarının paylaşılan bir nesneyi güncellemek isteyip istemediğinizi, ancak kilitlemeye çalışırken kilitlenip kilitlenmediğini düşünün.

Sadece bir avuç iş parçacığınız varsa, programınız düzgün çalışmayı durduracağı için bu hatalar muhtemelen açıktır. Gerektiğinde daha fazla iş parçacığı oluşturan bir iş parçacığı havuzunuz varsa, eski / sıkışmış iş parçacıkları fark edilmeyebilir ve süresiz olarak birikerek bellek sızıntısına neden olur. İş parçacıklarının uygulamanızda başka veriler kullanması muhtemeldir, bu nedenle doğrudan başvurdukları her şeyin toplanmasını da önler.

Oyuncak örneği olarak:

static void leakMe(final Object object) {
    new Thread() {
        public void run() {
            Object o = object;
            for (;;) {
                try {
                    sleep(Long.MAX_VALUE);
                } catch (InterruptedException e) {}
            }
        }
    }.start();
}

Çağrı System.gc()sizin gibi tüm, ama geçirilen nesne leakMeasla ölmeyecek.

(* Düzenlenmiş *)


1
@Spidey Hiçbir şey "sıkışmış" değildir. Çağıran yöntem derhal geri döner ve iletilen nesne hiçbir zaman geri alınmaz. Bu kesinlikle bir sızıntı.
Boann

1
Programınızın ömrü boyunca "çalışan" (veya uyuyan) bir iş parçanız olacaktır. Bu benim için bir sızıntı sayılmaz. Ayrıca havuz tamamen kullanmasanız bile sızıntı sayılmaz.
Spidey

1
@Spidey "Programınızın ömrü boyunca bir şeyiniz olacak. Bu benim için bir sızıntı sayılmaz." Kendini duyuyor musun
Boann

3
@Spidey Sürecin sızdırılmadığını bildiği belleği sayarsanız, işlem her zaman sanal adres alanındaki hangi sayfaların eşlendiğini izlediğinden, tüm yanıtlar yanlıştır. İşlem sona erdiğinde, işletim sistemi sayfaları boş sayfa yığınına geri koyarak tüm sızıntıları temizler. Bunu bir sonraki uç noktaya götürmek için, RAM yongalarındaki veya diskteki takas alanındaki fiziksel bitlerin hiçbirinin fiziksel olarak yanlış yerleştirilmediğini veya imha edilmediğini belirterek tartışılan herhangi bir sızıntıyı yenebilir, böylece bilgisayarı kapatabilirsiniz. ve herhangi bir sızıntıyı temizlemek için tekrar açın.
Boann

1
Bir sızıntının pratik tanımı, bilmediğimiz ve böylece yalnızca geri kazanılması için gerekli prosedürü gerçekleştiremediği için kaybolan hafızasıdır; tüm bellek alanını yıkıp yeniden inşa etmeliyiz. Bunun gibi hileli bir iş parçacığı, doğal olarak bir kilitlenme veya tehlikeli threadpool uygulamasıyla ortaya çıkabilir. Bu tür iş parçacıkları tarafından dolaylı olarak bile atıfta bulunulan nesnelerin artık toplanması engellenmektedir, bu nedenle programın ömrü boyunca doğal olarak geri kazanılmayacak veya tekrar kullanılamayacak belleğe sahibiz. Buna sorun diyorum; özellikle bir bellek sızıntısı.
Boann

10

Geçerli bir örnek iş parçacığı bir ortamda ThreadLocal değişkenleri kullanarak olabileceğini düşünüyorum.

Örneğin, diğer web bileşenleri ile iletişim kurmak, iş parçacığı tarafından oluşturulan iş parçacıklarına sahip olmak ve bir havuzda boşta kalanları korumak için Servlet'lerde ThreadLocal değişkenleri kullanmak. ThreadLocal değişkenleri, doğru şekilde temizlenmezse, muhtemelen aynı web bileşeni değerlerinin üzerine yazılıncaya kadar orada kalır.

Tabii ki, bir kez tanımlandığında, sorun kolayca çözülebilir.


10

Görüşmeci dairesel bir referans çözümü arıyor olabilir:

    public static void main(String[] args) {
        while (true) {
            Element first = new Element();
            first.next = new Element();
            first.next.next = first;
        }
    }

Çöp toplayıcıları saymakla ilgili klasik bir sorundur. O zaman kibarca JVM'lerin bu sınırlamaya sahip olmayan çok daha karmaşık bir algoritma kullandıklarını kibarca açıklarsınız.

-Wes Tarle


12
Çöp toplayıcıları saymakla ilgili klasik bir sorundur. 15 yıl önce bile Java ref sayımını kullanmadı. Ref. sayma GC'den daha yavaştır.
bestsss

4
Bellek sızıntısı değil. Sadece sonsuz bir döngü.
Esben Skov Pedersen

2
@Esben Her bir yinelemede, bir önceki firstyararlı değildir ve çöp toplanmalıdır. İçinde referans sayma (kendi başına), bunun üzerinde aktif bir referans yoktur, çünkü çöp toplayıcı, nesne freeed olmaz. Sonsuz döngü sızıntıyı azaltmak için burada: programı çalıştırdığınızda bellek süresiz olarak artacak.
rds

Wesley Tarle, döngünün sonsuz olmadığını varsayar . Hala bellek sızıntısı olur mu?
nz_21
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.