Soru Java ile bir bellek sızıntısı yaratma


Sadece bir röportaj yaptım ve Java ile bir bellek sızıntısı yaratması istendi. Söylemeye gerek yok, bir tane bile yaratmaya nasıl başlayacağına dair hiçbir fikre sahip olmayan çok aptalca hissettim.

Bir örnek ne olurdu?


2641


Menşei


Onlara Java'nın bir çöp toplayıcı kullandığını ve "bellek sızıntısı" tanımıyla ilgili biraz daha spesifik olmalarını istediğimi ve JVM hatalarını engellemeyi - Java'nın bellekte kaçıramayacağını oldukça C / C ++ aynı şekilde yapabilirsiniz. Nesne başvurusu olmalı bir yerde. - Darien
Çoğu cevapta insanların bu uç davaları ve hileleri aradıklarını ve noktayı (IMO) tamamen kaçırdıkları görülüyor. Hiçbir zaman tekrar kullanmayacak ve aynı zamanda bu referansları asla düşürmeyecek nesnelere gereksiz referanslar veren kodları gösterebilirler; Bu vakaların "gerçek" bellek sızıntıları olmadığı söylenebilir, çünkü bu nesnelerin etrafında hala referanslar vardır, ancak program bu referansları bir daha asla kullanmazsa ve asla düşürmezse, bu tamamen (ve en kötüsü) ile eşdeğerdir. gerçek bellek sızıntısı. - ehabkost
Dürüst olmak gerekirse, "Go" ile ilgili sorduğum benzer soruya -1 'e düştüğüne inanamıyorum. İşte: stackoverflow.com/questions/4400311/...   Temelde, bahsettiğim bellek sızıntıları OP'ye +200 tane oyu alan ve henüz "Go" nun da aynı soruna sahip olup olmadığını sormak için saldırıya uğrayan ve hakaret edilenler. Her nasılsa, bütün wiki-şeylerin o kadar iyi çalıştığından emin değilim. - SyntaxT3rr0r
@ SyntaxT3rr0r - darien'ın cevabı fanboy değil. Belli JVM'lerin belleğin sızdığı anlamına gelen hatalar olabileceğini açıkça itiraf etti. Bu, bellek sızıntılarına izin veren dil özelliğinden farklıdır. - Peter Recore
@ehabkost: Hayır, eşdeğer değiller. (1) Belleği geri alma yeteneğine sahipseniz, "gerçek bir sızıntıda" C / C ++ programınız tahsis edilen aralığı unutur, kurtarmanın güvenli bir yolu yoktur. (2) Sorunu profil oluşturma ile kolayca tespit edebilirsiniz, çünkü "bloat" ın hangi nesneleri içerdiğini görebilirsiniz. (3) Sonlandırılana kadar bir çok nesneyi tutan bir program iken, "gerçek bir sızıntı", kesin bir hatadır. could çalışmanın ne anlama geldiğinin kasti bir parçası olmak. - Darien


Cevaplar:


İşte, gerçek bir bellek sızıntısı (kod çalıştırarak erişilemeyen, ancak bellekte saklanan nesneler) saf Java'da oluşturmanın iyi bir yolu:

  1. Uygulama uzun süren bir iş parçacığı oluşturur (veya daha hızlı sızmak için bir iş parçacığı havuzu kullanın).
  2. İş parçacığı (isteğe bağlı olarak özel) ClassLoader aracılığıyla bir sınıf yükler.
  3. Sınıf, büyük bir bellek belleği tahsis eder (ör. new byte[1000000]), statik bir alana güçlü bir başvuru depolar ve sonra bir ThreadLocal içinde kendisine bir başvuru depolar. Ekstra belleğin ayrılması isteğe bağlıdır (Sınıf örneğinin sızdırılması yeterlidir), ancak sızıntı işini daha hızlı yapar.
  4. İş parçacığı, özel sınıfa veya yüklendiği ClassLoader'a yapılan tüm başvuruları temizler.
  5. Tekrar et.

Bu, ThreadLocal nesnesine bir başvuru tuttuğu için çalışır; bu, Sınıfına bir başvuruda bulunur ve bu da ClassLoader öğesine bir başvuru tutar. ClassLoader, sırayla yüklediği tüm Sınıflara bir referans tutar.

(Özellikle Java 7'den önce birçok JVM uygulamasında daha kötüydü, çünkü Sınıflar ve Sınıf Yükleyicileri doğrudan permene tahsis edildi ve hiçbir zaman GC'le karşılaşmadılar. Ancak, JVM'nin sınıf boşaltmayı nasıl ele aldığına bakılmaksızın, bir ThreadLocal hala bir Sınıf nesnesinin yeniden kazanılması.)

Bu modeldeki bir değişiklik, uygulama kapsayıcılarının (Tomcat gibi) herhangi bir şekilde ThreadLocals'ı kullanan uygulamaları sık sık yeniden dağıtırsanız, bir elek gibi belleği neden sızdırabileceğidir. (Uygulama kabı, Açıklandığı şekilde Konuları kullanır ve uygulamayı her yeniden başlattığınızda yeni bir ClassLoader kullanılır.)

Güncelleştirme: Birçok insan bunu sorduğundan, işte bu davranışı gösteren bazı örnek kod.


1935



+1 ClassLoader sızıntıları, genellikle veriyi dönüştüren 3. parti lib'lerin (BeanUtils, XML / JSON codec'leri) neden olduğu JEE dünyasındaki 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ışında yüklendiğinde, ancak sınıflarınıza (örneğin, önbelleğe alma) başvurular taşıdığında ortaya çıkabilir. Uygulamanızı kaldırdığınızda / yeniden dağıttığınızda, JVM, uygulamanın sınıf yükleyicisini (ve dolayısıyla yüklenen tüm sınıfları) toplayamaz. Bu nedenle, tekrarla birlikte uygulama sunucusu en sonunda borçlandırılır. Şansınız varsa ClassCastException ile bir ipucu z.x.y.Abc z.x.y.Abc'ye gönderilemez - earcam
tomcat TÜM yüklü sınıflarda hileleri kullanır ve TÜM statik değişkenler kullanır, tomcat çok fazla veriye ve kötü kodlamaya sahiptir (biraz zamana ihtiyaç duyar ve düzeltmeler gönderir), ayrıca iç (küçük) nesneler için önbellek olarak tüm akıllara durgunluk veren ConcurrentLinkedQueue, ConcurrentLinkedQueue.Node bile daha fazla bellek alan çok küçük. - bestsss
+1: Classloader sızıntıları bir kabus. Onları anlamaya çalışan haftalar geçirdim. Üzücü olan şey, @earcam'ın söylediği gibi, çoğunlukla 3. parti lib'lerden kaynaklanıyor ve çoğu profilci 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
@Nicolas: Emin misin? JRockit varsayılan olarak GC Class nesnelerini yapar ve HotSpot bunu yapmaz, ancak AFAIK JRockit hala bir ThreadLocal tarafından başvurulan bir Sınıf veya Sınıf Yükleyicisi GC olamaz. - Daniel Pryden
Tomcat sizin için bu sızıntıları tespit etmeye çalışacak ve onları uyarıda bulunacaktır: wiki.apache.org/tomcat/MemoryLeakProtection. En yeni sürüm bazen sızıntıyı sizin için giderir. - Matthijs Bierman


Statik alan tutma nesnesi referansı [esp final alanı]

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

çağrı String.intern() uzun String üzerinde

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();
}

JVM'nin çöp toplayıcısına ulaşılamaması gereken alanlaryerel yöntemlerle ayrılmış bellek gibi

Web uygulamalarında, bazı nesneler, uygulama açıkça durdurulup kaldırılıncaya kadar uygulama kapsamında depolanır.

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

Yanlış veya uygunsuz JVM seçenekleri, benzeri noclassgc Kullanılmayan sınıf çöp toplamalarını engelleyen IBM JDK seçeneği

Görmek IBM jdk ayarları.


1062



Bu bağlam ve oturum özelliklerinin "sızıntı" olduğuna katılmıyorum. Onlar sadece uzun ömürlü değişkenler. Ve statik final alanı az ya da çok sabittir. Belki büyük sabitlerden kaçınılmalıdır, ama buna bellek sızıntısı demenin adil olduğunu düşünmüyorum. - Ian McLaird
(Kapatılmamış) açık akışlar (dosya, ağ vb ...), sonlandırma sırasında (bir sonraki GC döngüsünden sonra olacak), gerçek için sızıntı yapmaz.close() genellikle bloke edici bir işlem olabileceği için sonlandırma ipinde çağrılmaz). Kapatmamak için kötü bir uygulama ama bir sızıntıya sebep olmaz. Kapatılmamış java.sql.Connection aynıdır. - bestsss
Çoğu sanırım JVM'lerinde, String sınıfının yalnızca kendi başına zayıf bir referansı varmış gibi görünür. intern hashtable içeriği. Bu şekilde olduğu Çöp düzgün toplanmış ve bir sızıntı değil. (ama IANAJP) mindprod.com/jgloss/interned.html#GC - Matt B.
Statik alan tutma nesne referansı [esp final alanı] bir bellek sızıntısı nedir? - Kanagavelu Sugumar
@ cHao Doğru. İçinde bulunduğum tehlike, Streams tarafından belleğin sızmasından kaynaklanan bir sorun değil. Sorun şu ki, onlar tarafından yeterince bellek sızmıyor. Bir sürü tutamı sızdırabilirsin, ama hala bol miktarda bellek var. Çöp Toplayıcı daha sonra tam bir koleksiyon yapmamaya karar verebilir, çünkü hala bol miktarda belleğe sahiptir. Bu, finalizer'ın çağrılmadığı anlamına gelir, dolayısıyla tutamaçlarınız tükenir. Sorun şu ki, finalizörler (genellikle) sızan akıntılardan hafızadan çıkmadan önce çalışırlar, ancak hafızanın dışında bir şeyden çıkmadan önce çağrılmayabilirler. - Patrick M


Yapılması gereken basit bir şey, bir HashSet'i yanlış (veya var olmayan) kullanmaktır. hashCode() veya equals()ve sonra "çoğaltmalar" eklemeye devam edin. Yinelenenleri olduğu gibi göz ardı etmek yerine, set yalnızca büyüyecek ve bunları kaldıramayacaksınız.

Bu kötü anahtarların / elemanların etrafta takılmasını istiyorsanız,

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.

390



Aslında, öğe sınıfı hashCode ve eşittir yanlış olsa bile, bir HashSet öğesinden kaldırabilirsiniz; sadece set için bir yineleyici olsun ve yineleme aracı aslında temel girdiler üzerinde değil, elemanlar üzerinde çalıştığından kaldırma yöntemini kullanın. (Unimplemented hashCode / equals olduğunu unutmayın. değil bir sızıntıyı tetiklemek için yeterli; Varsayılanlar basit nesne kimliğini uygular ve böylece öğeleri alabilir ve normal olarak kaldırabilirsiniz.) - Donal Fellows
@Donal söylemeye çalıştığım şey, sanırım bir bellek sızıntısı tanımına katılmıyorum. Yineleyici kaldırma tekniğinizin bir sızıntı altında damlama tavası olmasını (benzetmeye devam etmeyi) düşünürdüm; sızıntıdan bağımsız olarak sızıntı hala mevcuttur. - corsiKa
Katılıyorum, bu değil bir bellek "sızıntısı", çünkü hashset'e olan referansları kaldırabilir ve GC'nin tekme atmasını ve presto yapmasını bekleyebilirsiniz! bellek geri gider. - Mehrdad
@ SyntaxT3rr0r, sorunuzu, doğal olarak bellek sızıntılarına yol açan herhangi bir şey olup olmadığını sorduğum gibi yorumladım. Cevap hayır. Bu sorular, bir bellek sızıntısı gibi bir şey yaratmak için bir duruma uymanın mümkün olup olmadığını sorar. Bu örneklerin hiçbiri bir C / C ++ programlayıcısının anlayacağı şekilde bellek sızıntısı değildir. - Peter Lawrey
@Peter Lawrey: ayrıca, bunun hakkında ne düşünüyorsunuz? "Tahsis ettiğiniz hafızayı manuel olarak boşaltmayı unutmazsanız, C dilinde doğal olarak bellek sızıntısına sızan hiçbir şey yoktur". Bu entelektüel sahtekârlık için nasıl olurdu? Her neyse, yoruldum: son kelimeyi alabilirsin. - SyntaxT3rr0r


Aşağıda, unutulmuş dinleyicilerin standart durumunun yanı sıra, statik referanslar, hashmaps'daki sahte / değiştirilebilir tuşlar veya yaşam döngülerini sona erdirme şansı olmayan takma dişler dışında, Java'nın sızdırdığı açık olmayan bir durum olacaktır.

  • File.deleteOnExit()- her zaman ipi sızdırıyor, dize bir alt dizge ise, sızıntı daha da kötüdür (altta yatan char [] da sızdırılır) - Java 7 substring de kopyaları char[], o zaman geçerli değil; @Daniel, oylara gerek yok.

Genellikle işlenmemiş konuların tehlikesini göstermek için iş parçacığına konsantre olacağım, hatta sallanmaya bile dokunmak istemiyorum.

  • Runtime.addShutdownHook Ve kaldırılmamış ... ve daha sonra ThreadGroup sınıfındaki bir hata nedeniyle kaldırılmamış iş parçacığına ilişkin bir hata nedeniyle removeShutdownHook ile bile toplanamayabilir, ThreadGroup'u etkin bir şekilde sızdırabilir. JGroup'un GossipRouter'da sızıntı var.

  • Oluşturmak, ancak başlamak değil, Thread Yukarıdaki gibi aynı kategoriye girer.

  • Bir iş parçacığı oluşturmak ContextClassLoader ve AccessControlContextartı ThreadGroup Ve herhangi biri InheritedThreadLocalTüm bu referanslar, potansiyel yükler, sınıf yükleyicisi tarafından yüklenen tüm sınıflar ve tüm statik referanslar ve ja-ja'dır. Etki, özellikle süper basit özelliklere sahip tüm j.u.c.Executor çerçevesiyle görülebilir. ThreadFactory Arayüz, ancak çoğu geliştiricinin gizlenen tehlikenin bir ipucu yoktur. Ayrıca birçok kütüphane, istek üzerine (başlangıçta çok sayıda popüler endüstri kütüphanesi) konu başlıyor.

  • ThreadLocal önbelleğe; birçok durumda kötülük vardır. Eminim herkes ThreadLocal'a dayanan oldukça basit önbellekleri, kötü haberleri görüyor: emin olun eğer iş parçacığı hayattan daha fazlasını beklemeye alıyorsa ClassLoader, bu saf hoş bir sızıntı. Gerçekten gerekli olmadıkça ThreadLocal önbelleklerini kullanmayın.

  • çağrı ThreadGroup.destroy() ThreadGroup iş parçacığının kendisi olmadığında, ancak yine de ThreadGroups adlı çocuğa devam eder. ThreadGroup'un ebeveyninden ayrılmasını engelleyecek kötü bir sızıntı, ancak tüm çocuklar numarasız hale gelir.

  • WeakHashMap'in kullanılması ve değer (in) doğrudan anahtarı referans alır. Bu bir yığın dökümü olmadan bulmak zor bir şey. Bu genişletilmiş tüm için geçerlidir Weak/SoftReference Bu, korunan nesneye geri başvuruda bulunmaya devam edebilir.

  • kullanma java.net.URL HTTP (S) protokolü ile ve (!) kaynağından yükleme. Bu özel, KeepAliveCache Geçerli iş parçacığı bağlam sınıf yükleyicisini sızdıran sistem ThreadGroup içinde yeni bir iş parçacığı oluşturur. İş parçacığı, hiçbir canlı iş parçacığı olmadığı zaman ilk istek üzerine oluşturulur, bu nedenle ya şansınız olur ya da sızıntı olabilir. Sızıntı zaten Java 7'de sabittir ve iş parçacığını oluşturan kod, bağlam sınıf yükleyicisini düzgün bir şekilde kaldırır. Birkaç vaka daha var (ImageFetcher gibi, ayrıca sabit) benzer iplikler oluşturma.

  • kullanma InflaterInputStream geçen new java.util.zip.Inflater() yapıcıda (PNGImageDecoder örneğin) ve değil end() inflaterin Eh, eğer kurucuya sadece new, şans yok ... Ve evet, çağırıyor close() akışta, yapıcı parametresi olarak el ile geçtiyse, fışkırtıcıyı kapatmaz. Bu, kesinleştirici tarafından serbest bırakılacağından, gerekli gördüğü zaman gerçek bir sızıntı değildir. O ana kadar o ana bellek yediğinden, Linux oom_killer sürecini cezasızlıkla öldürmesine neden olabilir. Esas sorun, Java'daki sonlandırmanın çok güvenilir olmaması ve G1'in 7.0.2'ye kadar kötüleşmesi. Hikayenin Ahlaki: yerli kaynakları mümkün olan en kısa sürede serbest bırakın; finalizer sadece çok zayıf.

  • Aynı durumda java.util.zip.Deflater. Bu, Java'daki belleğin açılışı olduğu için çok daha kötüdür, yani her zaman yüzlerce KB yerel bellek ayırmak için 15 bit (maksimum) ve 8 bellek seviyesi (9 maks) kullanır. Neyse ki, Deflater yaygın olarak kullanılmaz ve benim bilgime göre JDK yanlış kullanmaz. Her zaman çağrı end() el ile oluşturursanız Deflater veya Inflater. Son ikisinin en iyi kısmı: bunları mevcut normal profil araçlarıyla bulamıyorsunuz.

(İstek üzerine karşılaştığım daha fazla zaman israfı ekleyebilirim.)

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


228



Creating but not starting a Thread... Yikes, bundan birkaç asır önce bu konuda çok ısırıldım! (Java 1.3) - leonbloy
@leonbloy, iş parçacığı düz bir şekilde doğrudan grup grubuna eklendiğinden daha da kötüydü, başlangıç ​​olarak çok sert bir sızıntı anlamına geliyordu. Değil sadece artırır unstarted saymak ancak iş parçacığının yok etmesini engeller (daha az kötülük ama yine de bir sızıntı) - bestsss
Teşekkür ederim! "Arayan ThreadGroup.destroy() ThreadGroup'un hiçbir parçasının olmadığı zaman ... " inanılmaz derecede ince bir böcek; Bunu saatlerce kovaladım, yoldan çıktım çünkü kontrol GUI'mdeki iş parçacığı numaralandırmanın hiçbir şey göstermediği, ancak iş parçacığı grubu ve muhtemelen en az bir çocuk grubu gitmeyecekti. - Lawrence Dol
@bestsss: Merak ediyorum, neden JVM kapamada çalıştığı göz önüne alındığında, neden bir kapatma kancası çıkarmak istersiniz? - Lawrence Dol


Buradaki örneklerin çoğu "çok karmaşık". Onlar kenar durumlardır. Bu örneklerle, programcı bir hata yaptı (eşittir / eşek kodu yeniden tanımlamamak gibi), ya da JVM / JAVA'nın bir köşesi (ısıl yük ile sınıf yükü) tarafından ısırıldı. Bence bu bir görüşmecinin istediği türden bir örnek değil, hatta en yaygın durumdur.

Ancak, bellek sızıntıları için daha basit durumlar var. Çöp toplayıcı sadece artık referans alınmayan şeyleri serbest bırakır. Java geliştiricileri olarak bizler belleği önemsemiyoruz. Gerektiğinde tahsis ederiz ve otomatik olarak serbest bırakılsın. İnce.

Ancak uzun ömürlü herhangi bir uygulama paylaşılan bir devlete sahip olma eğilimindedir. Her şey olabilir, statics, singleton ... Genellikle önemsiz olmayan uygulamalar karmaşık nesneler grafikleri oluşturma eğilimindedir. Sadece bir referansı null değerine ayarlamayı unutmak ya da daha sık bir koleksiyondan bir nesneyi kaldırmayı unutmak bir bellek sızıntısı yapmak için yeterlidir.

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

Hesaplamanın gerektirdiği önceki durumu saklayan karmaşık bir grafiğimiz olabilir. Ancak önceki devletin kendisi, daha önce devlet ile bağlantılıdır.

SQL bağlantılarını veya dosyalarını kapatmamız gerektiği gibi. Null'a uygun referanslar koymamız ve koleksiyondaki öğeleri kaldırmamız gerekiyor. Uygun önbellekleme stratejilerine sahip olacağız (maksimum bellek boyutu, eleman sayısı veya zamanlayıcı). Bir dinleyicinin bilgilendirilmesine izin veren tüm nesnelerin hem bir addListener hem de removeListener yöntemini sağlaması gerekir. Ve bu bildiriciler artık kullanılmadığında, dinleyici listelerini temizlemelidirler.

Bir bellek sızıntısı gerçekten gerçekten mümkün ve mükemmel tahmin edilebilir. Özel dil özelliklerine veya köşe durumlarına gerek yok. Bellek sızıntıları ya bir şeyin eksik olduğu ya da tasarım problemlerinin bile bir göstergesidir.


150



Diğer cevaplarda insanların bu uç davaları ve hileleri aradıklarını ve bu noktayı tamamen kaçırdıkları görülüyor. Bir daha kullanamayacak nesnelere gereksiz referanslar veren kodları gösterebilirler ve bu referansları asla kaldırmazlar; Bu vakaların "gerçek" bellek sızıntıları olmadığı söylenebilir, çünkü bu nesnelerin etrafında hala referanslar vardır, ancak program bu referansları bir daha asla kullanmazsa ve asla düşürmezse, bu tamamen (ve en kötüsü) ile eşdeğerdir. gerçek bellek sızıntısı. - ehabkost
@Nicolas Bousquet: "Bellek sızıntısı gerçekten de mümkün"   Çok teşekkür ederim. +15 destek. Güzel. Bu gerçeği söylediğim için, Go diliyle ilgili bir soruyla ilgili olarak şunu söyledim: stackoverflow.com/questions/4400311   Bu soru hala olumsuz downvotes :( - SyntaxT3rr0r
Java ve .NET'teki GC, bir anlamda, diğer nesnelere referans veren nesnelerin varsayım grafiğiyle ilgili olarak, diğer nesneleri "önemseyen" nesnelerin grafiğiyle aynıdır. Gerçekte, referans grafikte "bakım" ilişkilerini temsil etmeyen kenarların bulunması mümkündür ve bir nesnenin doğrudan veya dolaylı bir referans yolu olmasa bile başka bir nesnenin varlığını önemsemesi mümkündür. WeakReference) birinden diğerine var. Bir nesne referansı bir yedek parçaya sahip olsaydı, bir "hedefe önem veren" göstergesinin olması yararlı olabilirdi ... - supercat
... ve sisteme bildirimler sağla ( PhantomReference) Bir nesneyi bu konuda umursayan bir kimseye sahip olmadığı tespit edildi. WeakReference biraz yaklaşıyor, ancak kullanmadan önce güçlü bir referansa dönüştürülmelidir; Güçlü referans mevcutken bir GC döngüsü meydana gelirse, hedefin faydalı olduğu kabul edilecektir. - supercat
Bu benim görüşüme göre doğru cevap. Yıllar önce bir simülasyon yazdık. Bir şekilde yanlışlıkla önceki durumu bir bellek sızıntısı oluşturarak mevcut duruma bağladık. Son teslim tarihi nedeniyle bellek sızıntısını asla çözemedik ama belgeleyerek bir “özellik” haline getirdik. - nalply


Cevap, görüşmecinin istediklerini düşündüğü şeye tamamen bağlıdır.

Pratikte Java sızıntısı yapmak mümkün mü? Tabii ki, ve diğer cevaplarda çok fazla örnek var.

Fakat sorulmakta olan birden fazla meta soru var mı?

  • Teorik olarak "mükemmel" bir Java uygulaması sızıntılara karşı savunmasız mı?
  • Aday teori ile gerçeklik arasındaki farkı anlıyor mu?
  • Aday çöplerin nasıl işlediğini anlıyor mu?
  • Ya da çöp koleksiyonunun ideal bir durumda nasıl çalışması gerekiyor?
  • Yerli arayüzler aracılığıyla başka dilleri arayabileceklerini biliyorlar mı?
  • Diğer dillerde hafıza sızmasını biliyorlar mı?
  • Aday, bellek yönetiminin ne olduğunu ve Java'da sahnenin arkasında neler olduğunu bile biliyor mu?

Meta-sorunuzu "Bu röportajda kullanabileceğim bir cevap var" olarak okuyorum. Ve dolayısıyla, Java yerine görüşme becerilerine odaklanacağım. Bir röportajdaki bir soruya verilen cevabı bilmeme durumunun, Java sızıntısını nasıl yapacağınızı bilmeniz gereken bir yer olmaktan çok tekrarladığına inanıyorum. Yani, umarım, bu yardımcı olacaktır.

Mülakat için geliştirebileceğiniz en önemli becerilerden biri, soruları aktif olarak dinlemeyi ve görüşmeci ile niyetlerini çıkarmayı öğrenmektir. Bu sadece sorularını istedikleri şekilde cevaplamanıza izin vermez, aynı zamanda bazı önemli iletişim becerilerinizin olduğunu gösterir. Ve eşit derecede yetenekli birçok geliştirici arasında bir seçim yapıldığında, her seferinde cevap vermeden önce dinleyen, düşünen ve anlayan birini işe alırım.


133



Ne zaman bu soruyu sorduğumda, oldukça basit bir cevabı arıyorum - bir kuyruğa bakmaya devam et, nihayet yakın bir db vs yok, tek bir sınıf yükleyici / iş parçacığı ayrıntıları değil, gc'nin sizin için ne yapabileceğini ve yapamayacağını anladığını ima eder. Tahmin ettiğin işe bağlıyım sanırım. - DaveC
Lütfen soruma bir bakın, teşekkürler stackoverflow.com/questions/31108772/... - Daniel Newtown


Anlamadıysanız aşağıdakiler oldukça anlamsız bir örnektir. JDBC. Ya da en azından JDBC bir geliştiricinin kapanmasını nasıl bekliyor? Connection, Statement ve ResultSet uygulamadan vazgeçmek yerine, onları atmadan veya bunlara atıfta bulunmadan önce örnekleri 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ıdaki sorun şu ki Connection Nesne kapalı değildir ve bu nedenle fiziksel bağlantı açık kalacaktır, çöp toplayıcı gelene ve ulaşılamayacağını görene kadar. GC, finalize yöntem, ancak uygulanmayan JDBC sürücüleri var finalizeEn azından aynı şekilde değil Connection.close uygulanmaktadır. Ortaya çıkan davranış, bellekte toplanan erişilemeyen nesneler, bellekle ilişkili bellek (bellek dahil) nedeniyle geri alınacaktır. Connection nesne basitçe geri alınamaz.

Böyle bir durumda Connection'ler finalize yöntem her şeyi temizlemez, aslında veritabanı sunucusuna fiziksel bağlantının birkaç çöp toplama döngüsü süreceğini, ancak veritabanı sunucusunun sonunda bağlantının canlı olmadığını (eğer varsa) çözeceğini ve kapatılması gerektiğini bulabilir.

JDBC sürücüsü uygulansa bile finalizeSonlandırma sırasında istisnaların atılması mümkündür. Ortaya çıkan davranış, şu anda "hareketsiz" nesneyle ilişkili herhangi bir belleğin, finalize sadece bir kez çağrılmaya garantilidir.

Nesne sonlandırması sırasında yukarıdaki istisnalarla karşılaşma senaryosu, bir bellek sızıntısına - nesne dirilişine yol açabilecek başka bir senaryo ile ilgilidir. Nesne dirilişi, başka bir nesneden nihai hale getirilmeden nesneye güçlü bir referans oluşturarak, genellikle kasıtlı olarak yapılır. Nesne dirilişi yanlış kullanıldığında, diğer bellek sızıntıları kaynaklarıyla birlikte bir bellek sızıntısına yol açacaktır.

Birbirinden şaşıracak çok daha fazla örnek var -

  • Yönetme List yalnızca listeye eklediğiniz ve ondan silmediğiniz durumlarda (artık ihtiyacınız olmayan öğelerinizden kurtulmanız gerekir)
  • açılış Socketveya Files, ancak artık gerekmediklerinde onları kapatmaz (yukarıdaki örnekte olduğu gibi) Connection sınıf).
  • Java EE uygulamasını indirirken Singletons'u boşaltma. Görünüşe göre, singleton sınıfını yükleyen Classloader, sınıfa bir referansı koruyacak ve dolayısıyla tekil örnek asla toplanmayacaktır. Uygulamanın yeni bir örneği dağıtıldığında, genellikle yeni bir sınıf yükleyicisi oluşturulur ve eski sınıf yükleyici, tekilden dolayı var olmaya devam eder.

113



Genellikle bellek sınırlarına ulaşmadan önce maksimum açık bağlantı sınırına ulaşacaksınız. Bana neden bildiğimi sorma ... - Hardwareguy
Bunu yapmak için Oracle JDBC sürücüsü ünlüdür. - chotchki
@Hardwareguy Ben koymak kadar çok SQL veritabanlarının bağlantı sınırlarını vurmak Connection.close Tüm SQL çağrılarımın son bloğuna. Ekstra eğlence için, veritabanına çok fazla çağrıyı engellemek için Java tarafında kilitler gerektiren uzun süredir çalışan Oracle saklı yordamlarını aradım. - Michael Shopsin
@Hardwareguy Bu ilginç ancak tüm bağlantılarda gerçek bağlantı sınırlarının vurulması gerçekten gerekli değil. Örneğin, weblogic uygulama sunucusu 11g'de dağıtılan bir uygulama için bağlantı sızıntılarını büyük ölçüde gördüm. Ancak sızan bağlantıların toplanması için bir seçenek nedeniyle, veritabanı sızıntıları mevcutken bellek sızıntıları devam ediyordu. Tüm ortamlardan emin değilim. - Aseem Bansal
Deneyimimde, bağlantıyı kapatsanız bile bir bellek sızıntısı oluyor. Önce ResultSet ve PreparedStatement'ı kapatmanız gerekir. OutOfMemoryErrors nedeniyle, çalışmaya başlatana kadar saatlerce ve hatta günlerce çalıştıktan sonra art arda çökmüş bir sunucu vardı. - Bjørn Stenfeldt


Muhtemelen, potansiyel bir bellek sızıntısının en basit örneklerinden biridir ve nasıl önleneceği, 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;
}

Eğer onu kendiniz uyguluyorsanız, artık kullanılmayan dizi elemanını temizlemeyi düşünürdünüz (elementData[--size] = null)? Bu referans büyük bir nesneyi canlı tutabilir.


102



'elementData [- size] = null;' Takas yapıyor mu sanırım ... - maniek
Ve hafıza nerede sızıyor? - rds
@maniek: Bu kodun bir bellek sızıntısı sergilediğini ima etmek istemedim. Kasten nesne tutmayı önlemek için bazen belirgin olmayan kodun gerektiğini göstermek için ona alıntı yaptım. - meriton
RangeCheck (indeks) nedir? ? - Koray Tugay
Joshua Bloch bu örneği Yığınların basit bir uygulamasını gösteren Etkili Java'da verdi. Çok iyi bir cevap. - rents


Artık ihtiyacınız olmayan nesnelere başvurduğunuz her zaman bir bellek sızıntınız olur. Görmek Java programlarında bellek sızıntılarını işleme Bellek sızıntılarının kendilerini Java'da nasıl gösterdiği ve bununla ilgili neler yapabileceğiniz örnekleri.


63



Bunun bir "sızıntı" olduğuna inanmıyorum. Bu bir böcek ve programın ve dilin tasarımına göre. Bir sızıntı etrafında asılı bir nesne olurdu olmadan herhangi bir referans. - Mehrdad
@Mehrdad: Bu, tüm dillere tam olarak uymayan sadece bir dar tanım. Bunu tartışırım herhangi Bellek sızıntısı, programın zayıf tasarımından kaynaklanan bir hatadır. - Bill the Lizard
@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ğini anlamıyorum. Var Daha az Java'da herhangi bir tanıma göre bir bellek sızıntısı yaratmanın yolları. Kesinlikle geçerli bir soru. - Bill the Lizard
@ 31eee384: Programınız asla kullanamayacağı bir bellekteki nesneleri tutarsa, teknik olarak sızdırılmış bellek. Daha büyük sorunlara sahip olmanız aslında bunu değiştirmez. - Bill the Lizard
@ 31eee384: Olmayacağı bir gerçeği biliyorsan, o zaman olamaz. Program yazılı olduğu gibi asla verilere erişmeyecektir. - Bill the Lizard


İle bellek sızıntısı yapabiliyorsunuz sun.misc.Unsafe sınıf. Aslında bu servis sınıfı, farklı standart sınıflarında kullanılır. java.nio sınıfları). Bu sınıfın örneğini doğrudan oluşturamazsınızama sen yapabilirsin Bunu yapmak için yansıma kullanın.

Kod Eclipse IDE'de derlenmiyor - komutu kullanarak derleyin javac (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();
        }
    }

}

43



Tahsis edilen bellek çöp toplayıcı için görünmez - stemm
Ayrılan bellek de Java'ya ait değildir. - bestsss
Bu güneş / oracle jvm özel mi? Örneğin. bu IBM üzerinde çalışacak mı? - Berlin Brown
Bellek kesinlikle "Java'ya aittir", en azından i) 'nin başka bir kimseye sunulmaması anlamında, ii) Java uygulaması çıktığı zaman sisteme geri dönecektir. JVM'nin hemen dışında. - Michael Anderson
Bu (en azından son sürümlerde) tutulma içinde oluşturacaktır ancak derleyici ayarlarını değiştirmeniz gerekecektir: Pencere> Tercihler> Java> Derleyici> Hatalar / Uyarı> Onaylanmamış ve kısıtlı API kümesi Yasak referans (erişim kuralları) "Uyarı" ". - Michael Anderson