Java istifadə edərək yaddaş sızıntısı yaradır

Mən yalnız bir müsahibə oldum və Java istifadə edərək bir yaddaş sızıntısı yaratmaq istəndi.
Söylemeye gerek yok, mən hətta onu yaratmağa başlamaq üçün nə qədər kiçik bir düşüncəyə sahib deyiləm.

Nümunə nə olacaq?

2828
24 июня '11 в 19:11 2011-06-24 19:11 Mat B 24 İyun tarixində saat 19:11 'də təyin olundu. 2011-06-24 19:11
@ 53 cavab
  • 1
  • 2

Burada təmiz bir Java ilə real yaddaş sızıntısını (kodu çalıştırdığınızda əlçatmaz olan obyektləri yaddaşda saxlayan) yaratmaq üçün yaxşı bir yoldur:

  • Ərizə uzun bir iş parçası yaradır (və ya hətta daha sürətli axan bir mövzu hovuz istifadə edir).
  • Mövzu sinfi bir (isteğe bağlı olaraq özelleştirilebilir) ClassLoader aracılığıyla yükler.
  • Sinif böyük bir yığma yaddaş ehtiva edir (məsələn, new byte[1000000] ), statik sahədə güclü bir istinad saxlayır və sonra da özünü referendumu ThreadLocal-da saxlayır. Əlavə yaddaş ayırmaq lazım deyil (bir sinif nümunəsi sızdırmaq kifayətdir), amma bu, sızdırma işini daha da sürətləndirəcəkdir.
  • Mövzu, yüklənmiş olduğu istifadəçi sinifinə və ya ClassLoader'a olan bütün istinadları təmizləyir.
  • Təkrarlayın.

Bu, ThreadLocal öz növbəsində öz ClassLoader-ə istinad edən bir sinifə istinad edən bir obyektə istinad saxladığı üçün işləyir. ClassLoader, öz növbəsində, bütün yüklənmiş siniflərə bir keçid saxlayır.

ClassV və ClassLoaders birbaşa icazə verildi və GC'd olmadıqları üçün JVM-nin bir çox tətbiqində, xüsusilə Java 7-dən daha pis idi. Lakin JVM sinif boşaltma işini necə idarə etdiyindən asılı olmayaraq, ThreadLocal hələ də obyektin qaytarılmasını maneə törədir sinif.)

Bu nümunənin bir çeşidi niyə tətbiqi konteynerlər (məsələn, Tomcat) eləcə də eleklər kimi sızan ola bilər, əgər tez-tez hər hansı bir şəkildə ThreadLocals istifadə edən tətbiqləri köçürsəniz. Tətbiq konteynerləri mövzuları açıqlandığı kimi tətbiq etdiyindən və tətbiqi yenidən tətbiq etdikdən sonra, yeni ClassLoader istifadə olunur.)

Yeniləmə . Bir çox insan bunun üçün xahiş etməyə davam etdiyinə görə, bu davranışı hərəkətdə göstərən bir nümunə kodudur .

2071
24 июня '11 в 21:05 2011-06-24 21:05 Cavab, Daniel Pryden tərəfindən 24 iyun 'da 21:05' də verildi. 2011-06-24 21:05

[Esp son sahə] obyektinə istinad göstərən statik bir sahə.

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

Uzun bir simli üçün String.intern() çağırın

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

(Bağlı olmayan) açıq axınlar (fayl, şəbəkə və s.)

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

Bağlı əlaqələr

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

Yerli metodlardan istifadə edərək ayrılan yaddaş kimi JVM zibil kollektorlarından əlçatmazdır

Veb ərizələrində ərizə aydın şəkildə dayandırılmış və ya silinməyincə tətbiq obyektlərində saxlanılır.

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

IBM JDK-da noclassgc parametri kimi yanlış və ya uyğun olmayan JVM parametrləri , istifadə edilməmiş klass zibil yığımını maneə törədir

IBM jdk ayarlarına baxın.

1113
01 июля '11 в 16:33 2011-07-01 16:33 Cavab Prashant Bhate tərəfindən 01.07.2011 16:33 tarixində verilir

Sadə bir vəzifə, HashSet'i yanlış (və ya olmayan) hashCode() equals() və sonra "dublikatları" əlavə etməyə davam edir. Çoğaltıcıları görməməzlikdən asılı olmayaraq, dəst yalnız böyüyəcək və onları aradan qaldırmaq mümkün olmayacaq.

Bu pis tuşların / əşyaların asılmasını istəyirsinizsə, kimi statik bir sahəni istifadə edə bilərsiniz

 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. 
416
24 июня '11 в 19:16 2011-06-24 19:16 Cavab Peter Lawrey tərəfindən 24 İyun 2011 tarixində 19:16 2011-06-24 19:16 tarixində verilir

Aşağıda unudulmaz bir vəziyyət olacaq: unutulmuş dinləyicilərin standart vəziyyətinə, statik bağlantılara, hashmapsda saxta / dəyişən açarlara və ya həyat dövrünə son vermək üçün heç bir şans olmadan sıxışan axınlara əlavə olaraq Java sızdırır.

  • File.deleteOnExit() - hər zaman bir simli itirir, simli bir substrinq olsa, sızıntı daha da pisləşir (saxlama char []) - substringdə Java 7 nüsxə char[] , belə ki, sonrakı bir versiya tətbiq edilmir; @Daniel, buna baxmayaraq heç bir səs lazım deyil.

Mən, əsasən, nəzarətsiz axınların təhlükəsini göstərmək, hətta dalğalara toxunmaq istəməyəcəyik.

  • Runtime.addShutdownHook və silinməyin ... və hətta evenShareoutHook ilə ThreadGroup sinifində birləşməmiş iş parçaları ilə bağlı bir səhv üzündən toplana və ThreadGroup-nun səmərəli şəkildə sızması mümkün deyil. JGroup, GossipRouter'da bir sızıntıya sahib.

  • Yaratmaq, lakin başlamaması, Thread yuxarıdakı kateqoriyaya daxil olur.

  • ContextClassLoader yaradıcısı ContextClassLoaderAccessControlContext , üstəgəl ThreadGroup və hər hansı ThreadGroup devralır, bütün bu əlaqələr, həmçinin sinif loader və bütün statik bağlantılar və ja-ja tərəfindən yüklənən bütün siniflər kimi potensial sızıntılardır. Təsir xüsusilə super sadə " ThreadFactory interfeysinə malik olan bütün jucExecutor infrastrukturu ilə fərqlənir, lakin əksər developers gizli təhlükələr haqqında heç bir fikri yoxdur. Həmçinin, bir çox kitabxanalar tələb olunan mövzuları (çox sayda sənayelə xüsusi kütləvi kitabxanalar) idarə edirlər.

  • ThreadLocal önbellekleri; bir çox hallarda bu pisdir. Əminəm ki, hər kəs bir neçə sadə "ThreadLocal" əsaslı önbelleklər, habelə pis xəbərlər görmüşdür: əgər axın gözləniləndən daha uzun sürsə, həyat ClassLoader kontekstindədir, bu gözəl təmiz az sızma. Bu həqiqətən lazım olsa ThreadLocal önbelleklerini istifadə etməyin.

  • ThreadGroup.destroy() çağırışı, ThreadGroup.destroy() mövzu yoxdur, ancaq hələ də uşağın mövzu qruplarını saxlayır. Yanlış sızma, ThreadGroup-nun ana tərəfdən çıxarılmasını maneə törədir, lakin bütün uşaqlar qeyri-sayılmalı olurlar.

  • WeakHashMap istifadə və dəyəri (in) birbaşa əsas istinad edir. Çöllər olmadan tapmaq çətindir. Bu qorunan obyektə etibarlı keçid saxlaya biləcək bütün uzun Weak/SoftReference tətbiq olunur.

  • HTTP (S) protokolü ilə java.net.URL istifadə edərək, java.net.URL (!) java.net.URL . Bu xüsusda, KeepAliveCache , Mövzu qrupunun sistemində yeni bir mövzu yaradır, bu da mövcud mövzu kontekstində stream yükləyicisini sızdırır. Birbaşa canlı axın olmadığında, axın ilk tələbi ilə yaradılır, belə ki, şanslı və ya yalnız sızan ola bilər. Sızıntı artıq Java 7-də müəyyənləşdirilmişdir və axın yaradan kod düzgün yükləyicini rədd edir. Bir neçə daha çox hal var ( ImageFetcher kimi , həmçinin sabit) oxşar mövzuları yaradır.

  • InflaterInputStream (məsələn, PNGImageDecoder ) new java.util.zip.Inflater() köçürmək və InflaterInputStream new java.util.zip.Inflater() InflaterInputStream istifadə edin. Bəli, əgər konstruktoru yalnız new ilə new , şansınız yoxdur ... Və buna görə də, əmanətdəki close() çağrı, bir konstruktor parametri olaraq əllə ötürülürsə, inflatorni bağlamır. Bu gerçək sızıntı deyil, çünki bu, yekunlaşdırıcı kimi sərbəst buraxılacaq ... zəruri hesab etdiyi zaman. Onun doğma yaddaşını çox yeyənə qədər o, Linux oom_killer'i cəzasızlıqla öldürə bilər. Əsas problem isə Java-nın yekunlaşdırılmasının çox etibarsız olduğunu və G1-nin 7.0.2-ə qədər pisləşdiyini göstərir. Hekayənin əxlaqı: ən qısa zamanda yerli qaynaqlarınızı azad edin; finalist çox pis.

  • Eyni halda java.util.zip.Deflater . Deflater aç Java yaddaşı olduğundan bu, daha pisdir. Həmişə 15 və nbsp istifadə edir; bit yaddaş kartı və 8 yaddaş səviyyəsini (9 - maks.), bir neçə yüz KB yaddaşa ayırır. Xoşbəxtlikdən, Deflater geniş istifadə edilmir və bildiyim qədər JDK sui-istifadədən azaddır. Əl ilə bir Deflater və ya Inflater halda həmişə end() Inflater . Son iki ən yaxşı hissəsi: onları adi profil qurma vasitələrində tapa bilməzsiniz.

(İstədiyində rastlaşdığım bir neçə parça əlavə edə bilərsiniz.)

Uğurlar və təhlükəsiz qalmaq; sızmalar pisdir!

248
30 июня '11 в 22:45 2011-06-30 22:45 Cavab 30 iyun, 2011-ci il saat 22: 45-də, 2011-06-30 22:45 tarixində verilir

Burada nümunələrin əksəriyyəti "çox mürəkkəbdir". Bunlar çox həssas hadisələrdir. Bu nümunələrdə proqramçı bir səhv etdi (məsələn, bərabər / hashcode'yi əvəz etmir) və ya JVM / JAVA (statik ilə bir sinif yükləmə ...) köşesi halda bitdi. Hesab edirəm ki, bu müsahibəçinin istədiyi nümunə deyil, hətta ən ümumi haldır.

Amma həqiqətən daha sadə yaddaş sızması hadisələri var. Zibil toplayıcısı yalnız daha çox əlaqələr olmadığı üçün azad edir. Biz, Java developers kimi, yaddaşa əhəmiyyət vermirik. Biz onu lazım olduğu kimi yayırıq və avtomatik olaraq buraxılmasına imkan veririk. Yaxşı

Ancaq uzun ömürlü bir tətbiq, bir qayda olaraq, ümumi vəziyyətə sahibdir. Hər şey ola bilər, statics, singletons ... Tez-tez qeyri-trivial applications mürəkkəb obyekt graphs tərtib edirlər. Sadəcə sıfıra istinad göstərməyi unutma və ya daha tez-tez yaddaşdan sızma səbəb olmaq üçün toplanan bir obyekti unutmağı unutmayın.

Əlbəttə ki, hər cür dinləyicilər (məsələn, UI dinləyiciləri), önbelleklər və ya uzun ömürlü ümumi dövlət düzgün işləmədikdə yaddaşın sızmasına səbəb olur. Bunu anlamaq lazımdır ki, bu, bir Java işi və ya zibil toplayıcısı ilə problem deyil. Bu dizayn məsələsidir. Biz dinləyicini uzunmüddətli bir obyektə əlavə etdiyimiz dizaynı hazırlayırıq, lakin artıq ehtiyac duyulduqda dinləyicidən çıxarmırıq. Nesneleri önbelleğe alırıq, ancaq onları önbellekten ayırmaq üçün heç bir strategiya yoxdur.

Bəlkə də hesablamalar üçün lazım olan əvvəlki dövləti saxlayan kompleks bir grafik var. Lakin əvvəlki dövlət özü dövlətlə bağlıdır və s.

Qoşulma və ya SQL fayllarını necə bağlamalıyıq. Doğru sıfır istinadını təyin etməli və elementləri kolleksiyadan çıxarmalıyıq. Biz doğru caching strategiyaları (maksimum yaddaş, elementlərin sayı və ya timers) olacaq. Bir dinləyiciyə xəbər verən bütün obyektlər addListener və removeListener üsullarını təmin etməlidir. Və bu bildirişçilər artıq istifadə edilmədikdə, dinləyicilərin siyahısını təmizləməlidirlər.

Bir yaddaş sızıntısı həqiqətən mümkün və olduqca proqnozlaşdırıla bilir. Xüsusi dil xüsusiyyətlərinə və ya buruq vəziyyətlərə ehtiyac yoxdur. Yaddaş sızması ya bir şeyin itkin olduğunu göstərir və ya hətta dizayn problemləri.

174
01 июля '11 в 10:54 2011-07-01 10:54 Cavab Nicolas Bousquet tərəfindən 01 iyul 'da saat 01: 11' də verilir 2011-07-01 10:54

Cavab tamamilə müsahibənin istədiklərini düşündüyünə bağlıdır.

Java sızdırmazlığı ilə məşğul olmaq mümkündürmü? Əlbəttə, bu həqiqətdir və digər cavablarda bir çox nümunələr var.

Ancaq soruşulacaq bir neçə meta sual var?

  • Teorik olaraq bir "mükəmməl" java tətbiqinin sızması həssasdır?
  • Namizəd teoriya və gerçəklik arasındakı fərqi anlaya bilirmi?
  • Namizədin çöp toplama işinin necə başa düşülməsinə kömək edirmi?
  • Yoxsa ideal vəziyyətdə zibil toplama işi necə olmalıdır?
  • Bilirlərmi ki, onlar öz interfeysləri ilə digər dillərə müraciət edə bilərlər?
  • Digər dillərdə yaddaşın sızması bilirlərmi?
  • Namizədin yaddaş idarəsinin nə olduğunu və Java-nın səhnələrinin arxasında nə baş verə biləcəyini bilirmi?

"Bu müsahibə vəziyyətində hansı cavabı istifadə edə bilərəm?" Kimi meta sualınızı oxuyuram. Və buna görə də Java əvəzinə iş müsahibə bacarıqlarına diqqət çəkəcəyəm. İnanıram ki, müsahibədə sualın cavabını bilmirsinizsə, vəziyyəti təkrarlamaq ehtimalı çox olduğuna inanıram, doğru yerdəki bir Java sızıntısını necə qurmağınızdan daha çox olması lazımdır. Ümid edirəm ki, bu kömək edəcək.

Müsahibə üçün inkişaf edə biləcəyiniz ən əhəmiyyətli bacarıqlardan biri, sualları fəal şəkildə dinləmək və niyyətlərini çıxarmaq üçün müsahibə ilə necə işləməyi öyrənməkdir. Bu, onların suallarını istədiyiniz şəkildə cavablandırmaqla yanaşı, mühüm ünsiyyət bacarıqlarınız olduğunu göstərir. Və bir çox qabiliyyətli inkişaf edənlər arasında seçim etmək üçün gəldikdə, hər dəfə reaksiya verməzdən əvvəl dinləyən, düşünür və anlayır.

142
01 июля '11 в 23:06 2011-07-01 23:06 Cavab PlayTank tərəfindən 01.07.2011 tarixində 23:06 2011-07-01 23:06 tarixində verilir

JDBC-ni başa düşmürsəniz, aşağıda göstərilən bir nöqtədir . Və ya, ən azı, JDBC geliştiricinin Connection , StatementResultSet nümunələrini bağlamasını və ya istinad ResultSet əvvəl, finalize tətbiqinə istinad etməkdən çəkinməsini gözlədiyi kimi.

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

Yuxarıda göstərilən problem Connection obyektinin bağlanmamasıdır və buna görə də fiziki əlaqə çöp toplayıcısı görünənə qədər açıq qalacaq və bunun mümkün olmadığını görür. GC, finalize metodunu çağırır, lakin Connection.close tətbiq olunduğu kimi heç də ən azı finalize JDBC sürücüləri var. Nəticə etibarı ilə, əlçatmaz obyektlərin çatışmazlığı səbəbindən yaddaş bərpa olunmasına baxmayaraq, Connection obyekti ilə əlaqəli resurslar (yaddaş daxil olmaqla) sadəcə sabit olmaya bilər.

Bu halda, Connection finalize metodu hər şeyi təmizləməzsə, həqiqətən veritabanı server bir fiziki bir verilənlər bazası server bir neçə zibil toplanması dövründə olacaq ki, vergi bazası server nəticədə əlaqə canlı deyil (əgər var) və bağlanmalıdır.

JDBC sürücüsünün finalize bilməsi halında, istisnalar finalize sonra istisna edilə bilər. Nəticədə davranış, indi "aktiv olmayan" obyektlə əlaqəli hər hansı bir yaddaşın müəyyənləşdirilməyəcəyi, çünki finalize bir dəfə bir dəfə çağırılacaqdır.

Nasiyanın tamamlanması zamanı yuxarıda göstərilən istisna aşkarlama ssenarisi yaddaş sızıntısının bərpasına səbəb ola biləcək başqa bir ssenari ilə əlaqələndirilir. Bir obyektin dirilməsi tez-tez başqa bir obyektdən tamamlanmalı olan bir obyektə güclü bir keçid yaratmaqla qəsdən həyata keçirilir. Bir dirilmə obyekti yanlış istifadə edildikdə, yaddaş sızıntısının digər qaynaqları ilə birlikdə yaddaş sızıntısına gətirib çıxaracaq.

Çağırış edə biləcəyiniz bir çox nümunə var, məsələn,

  • Yalnız siyahı əlavə etdiyiniz və onu silməyin (baxmayaraq lazımsız elementlərdən qurtulmanız lazım olsa) və ya
  • Socket və ya File açılması, lakin artıq ehtiyac olmadıqda onları bağlamaması (sinif Connection ilə yuxarıda göstərilən nümunəyə bənzər).
  • Java EE proqramı təqdim edərkən təklisini yükləməyin. Göründüyü kimi, singleton sinifini yükləyən Classloader sinifə bir istinad saxlayacaq və bu səbəbdən bir tək nümunə tərtib edilməyəcəkdir. Bir tətbiqin yeni bir nümunəsi yerləşdirildikdə, yeni bir sinif yükləyicisi yaradılıb və köhnə yük yükləyicisi təklinə görə mövcud olmağa davam edəcəkdir.
121
24 июня '11 в 19:21 2011-06-24 19:21 Cavab Vineet Reynolds tərəfindən 24 iyun 'da 19:21' də verildi 2011-06-24 19:21

Yəqin ki, potensial yaddaş sızmasının ən sadə nümunələrindən biri və bunun qarşısını almaq ArrayList.remove (int) tətbiqidir:

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

Özünüzü tətbiq etsəniz, artıq istifadə edilən bir element elementini təmizləməyi elementData[--size] = null ( elementData[--size] = null )? Bu link bir canlı obyekt saxlaya bilər ...

107
25 июня '11 в 0:28 2011-06-25 00:28 Cavab 25 iyun 11: 00 -da veriləcək

Artıq ehtiyac duymayan obyektlərə hər dəfə daxil olduqda, bir yaddaş sızıntısı var. Yaddaş sızıntısının Java -da necə göründüyünü və bununla nə edə biləcəyinizın nümunələri üçün Java proqramlarında yaddaş sızıntısını idarə etmək baxın.

64
24 июня '11 в 19:18 2011-06-24 19:18 cavab 24 iyun 2011-ci il saat 19: 18-da Bill Kərim tərəfindən verilmişdir

Sun.Misc.Unsafe sinifini istifadə edərək yaddaşını sızdıra bilərsiniz. Əslində bu xidmət sinifi müxtəlif standart siniflərdə istifadə olunur (məsələn, java.nio dərsləri). Bu sinifin bir nümunəsini birbaşa yaratmaq mümkün deyil , ancaq bunun üçün əksini istifadə edə bilərsiniz.

Kod, Eclipse IDE-də tərtib edilmir - javac komandasını istifadə edərək kompilyasiya edin (kompilyasiya zamanı xəbərdarlıq alacaqsınız)

 import java.> 
46
11 июля '11 в 15:55 2011-07-11 15:55 cavab 11 iyul 11: 00 -da verilir

Cavabımı buradan kopyalayabilirəm: Java-da yaddaş sızıntısını yaratmanın ən asan yolu?

"Kompüter fənnində yaddaş sızması (və ya bu kontekstdə sızma) bir kompüter proqramı yaddaşını tükəndiyi ancaq əməliyyat sisteminə qaytaramadığında meydana gəlir." (Vikipediya)

Sadə cavab: edə bilməzsiniz. Java avtomatik yaddaşı idarə edir və ehtiyacınız olmayan resursları azad edir. Bunu dayandıra bilməzsiniz. O, həmişə resursları azad edəcəkdir. Əl ilə yaddaş idarə edən proqramlarda bu fərqlidir. Malloc () istifadə edərək, C yaddaşında bir yaddaş ala bilmir. Yaddaşdan azad olmaq üçün, malloc tərəfindən qaytarılan bir pointer və pulsuz () çağrısı lazımdır. Amma artıq bir göstəriciyə malik deyilsinizsə (yazılı və ya xidmət ömrü aşıldı), təəssüf ki, bu yaddaşı azad edə bilməzsiniz və buna görə də bir yaddaş sızıntısı var.

Bu günə qədər mənim tərifimdə olan bütün digər cavablar yaddaş sızması deyil. Hamısı xatirələri çox tez bir şəkildə boşa daşımaq üçün çalışırlar. Amma istənilən vaxt siz yaratdığınız obyektləri kənarlaşdırmaq və yaddaşı azad edə bilərsiniz → NO LEAK. Acconrad cavabını olduqca yaxındır, buna baxmayaraq, mən həll etməliyəm ki, onun həlli səmərəli şəkildə "zibil toplayıcısını" söndürməklə , onu sonsuz bir dövrəyə çevirir).

Uzun cavab: JNI istifadə edərək Java kitabxanası yazaraq bir yaddaş sızıntısı əldə edə bilərsiniz ki, bu da əl ilə yaddaş idarəçiliyinə malik ola bilər və bu səbəbdən yaddaş sızması var. Bu kitabxana deyirsinizsə, java prosesi yaddaşını sızdırır. Ya da JVM-də səhvlər ola bilər, belə ki, JVM yaddaşını itirir. Yəqin ki, JVM-də səhvlər var, bəlkə də bəziləri məlumdur, çünki zibil toplanması çox əhəmiyyətsiz deyil, lakin hələ də səhvdir. Dizaynla bu mümkün deyil. Belə bir səhv səbəb olan bəzi Java kodunu tələb edə bilər. Üzr istəyirik, bir şey bilmirəm və hər halda bu Java-nın növbəti versiyasında səhv ola bilməz.

41
02 июля '11 в 13:23 2011-07-02 13:23 Cavab yankee tərəfindən verilir 02 İyul 2011 'də 13:23 2011-07-02 13:23

Здесь простой/зловещий через http://wiki.eclipse.org/Performance_Bloopers#String.substring.28.29 .

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

Поскольку подстрока относится к внутреннему представлению оригинальной, гораздо более длинной строки, оригинал остается в памяти. Таким образом, если у вас есть StringLeaker в игре, у вас есть целая оригинальная строка в памяти, даже если вы можете подумать, что вы просто держитесь за односимвольную строку.

Способ избежать нежелательной ссылки на исходную строку состоит в том, чтобы сделать что-то вроде этого:

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

При добавлении плохой ситуации вы также можете .intern() подстроку:

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

При этом сохраняется как исходная длинная строка, так и производная подстрока в памяти даже после того, как экземпляр StringLeaker был отброшен.

34
ответ дан Jon Chambers 21 июля '11 в 20:51 2011-07-21 20:51

Возьмите любое веб-приложение, работающее в любом контейнере сервлетов (Tomcat, Jetty, Glassfish, что угодно...). Обновите приложение 10 или 20 раз подряд (этого может быть достаточно, чтобы просто коснуться WAR в каталоге autodeploy сервера.

Если кто-либо действительно не протестировал это, есть вероятность, что вы получите OutOfMemoryError после нескольких повторных развертываний, потому что приложение не позаботится о том, чтобы очистить себя. Вы можете даже найти ошибку на своем сервере с этим тестом.

Проблема в том, что срок службы контейнера больше, чем срок службы вашего приложения. Вы должны убедиться, что все ссылки, которые контейнер может иметь на объекты или классы вашего приложения, могут быть собраны в мусор.

Если есть только одна ссылка, сохранившаяся без развертывания вашего веб-приложения, соответствующий загрузчик классов и, следовательно, все классы вашего веб-приложения не могут быть собраны в мусор.

Потоки, запущенные вашим приложением, переменные ThreadLocal, добавление журналов являются некоторыми из обычных подозреваемых, которые вызывают утечку загрузчика.

33
ответ дан Harald Wellmann 04 июля '11 в 1:41 2011-07-04 01:41

Общим примером этого в GUI-коде является создание виджета/компонента и добавление слушателя к некоторому объекту с статическими/прикладными областями, а затем не удаление слушателя при уничтожении виджета. Вы получаете не только утечку памяти, но и производительность, так как когда вы слушаете события пожаров, все ваши старые слушатели также вызываются.

33
ответ дан pauli 28 июня '11 в 16:47 2011-06-28 16:47

Может быть, используя внешний собственный код через JNI?

С чистой Java это почти невозможно.

Но это касается "стандартного" типа утечки памяти, когда вы больше не можете обращаться к памяти, но она по-прежнему принадлежит приложению. Вместо этого вы можете хранить ссылки на неиспользуемые объекты или открывать потоки, не закрывая их впоследствии.

30
ответ дан Rogach 24 июня '11 в 19:14 2011-06-24 19:14

У меня была хорошая "утечка памяти" по отношению к PermGen и синтаксическому анализу XML один раз. Используемый нами синтаксический анализатор XML (я не помню, какой он был) использовал имена тегов String.intern(), чтобы ускорить сравнение. У одного из наших клиентов была отличная идея хранить значения данных не в атрибутах или тексте XML, а в качестве тэгов, поэтому у нас был документ вроде:

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

Фактически, они не использовали числа, а более длинные текстовые идентификаторы (около 20 символов), которые были уникальными и приходили со скоростью 10-15 миллионов в день. Это составляет 200 МБ мусора в день, который больше никогда не нужен, и никогда не GCed (так как он находится в PermGen). У нас был permgen установлен на 512 Мб, поэтому потребовалось около двух дней для исключения из памяти (OOME), чтобы прибыть...

28
ответ дан Ron 30 июня '11 в 11:39 2011-06-30 11:39

Недавно я столкнулся с ситуацией утечки памяти, вызванной способом log4j.

Log4j имеет этот механизм под названием Вложенный диагностический контекст (NDC) , который является инструментом для различения данных с чередованием журналов из разных источников. Гранулярность, с которой работает NDC, - это потоки, поэтому она различает выходные данные из разных потоков отдельно.

Для хранения тегов, связанных с потоком, класс log4j NDC использует Hashtable, который связан с самим объектом Thread (в отличие от идентификатора потока), и, таким образом, до тех пор, пока тег NDC не останется в памяти всех объектов, объекта потока также остаются в памяти. В нашем веб-приложении мы используем NDC для тегов logoutputs с идентификатором запроса, чтобы отличать журналы от одного запроса отдельно. Контейнер, который связывает тег NDC с потоком, также удаляет его, возвращая ответ от запроса. Проблема возникла, когда во время обработки запроса был создан дочерний поток, что-то вроде следующего кода:

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

Таким образом, контекст NDC был связан с встроенным потоком, который был порожден. Объект потока, который был ключом для этого контекста NDC, является встроенным потоком, который имеет огромный объект LIST, зависающий от него. Поэтому даже после того, как поток закончил делать то, что он делал, ссылка на огромный список была сохранена в контексте HDC HDC, что вызвало утечку памяти.

22
ответ дан Puneet 21 июля '11 в 22:39 2011-07-21 22:39

Я думал, что было интересно, что никто не использовал примеры внутреннего класса. Если у вас есть внутренний класс; он по своей сути поддерживает ссылку на содержащий класс. Конечно, это не технически утечка памяти, потому что Java в конечном итоге очистит ее; но это может привести к тому, что классы будут работать дольше, чем ожидалось.

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

Теперь, если вы вызываете Example1 и получаете пример 2, отбрасывающий Example1, у вас по-прежнему будет ссылка на объект Example1.

 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. } } 

Я также слышал слухи, что если у вас есть переменная, которая существует дольше определенного времени; Java предполагает, что он всегда будет существовать и на самом деле никогда не будет пытаться его очистить, если он больше не может быть достигнут в коде. Но это совершенно непроверено.

21
ответ дан Suroot 09 июля '11 в 4:23 2011-07-09 04:23

Какая утечка памяти:

  • Это вызвано ошибкой или плохим дизайном.
  • Это пустая трата памяти.
  • Со временем ухудшается.
  • Сборщик мусора не может его очистить.

Типичный пример:

Кэш объектов - хорошая отправная точка, чтобы повредить вещи.

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

Ваш кеш растет и растет. И довольно скоро вся база данных всасывается в память. В лучшем дизайне используется LRUMap (только хранит недавно использованные объекты в кеше).

Конечно, вы можете сделать вещи намного сложнее:

  • с помощью конструкций ThreadLocal .
  • добавление дополнительных сложных деревьев ссылок .
  • или утечки, вызванные сторонними библиотеками .

Что часто происходит:

Если этот объект Info имеет ссылки на другие объекты, которые снова имеют ссылки на другие объекты. В некотором смысле вы также можете считать, что это какая-то утечка памяти (вызвана плохим дизайном).

21
ответ дан bvdb 18 июля '14 в 22:24 2014-07-18 22:24

Создайте статическую карту и продолжайте добавлять к ней жесткие ссылки. Те никогда не будут GC'd.

 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); } } 
18
ответ дан duffymo 24 июня '11 в 19:17 2011-06-24 19:17

Интервьюер, вероятно, искал круговую ссылку, такую ​​как код ниже (который, кстати, только утечка памяти в очень старых JVM, которые использовали подсчет ссылок, что больше не так). Но это довольно неопределенный вопрос, поэтому это прекрасная возможность продемонстрировать ваше понимание управления памятью JVM.

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

Затем вы можете объяснить, что при подсчете ссылок вышеуказанный код будет утечка памяти. Но большинство современных JVM больше не используют подсчет ссылок, большинство используют сборщик мусора, который фактически собирает эту память.

Далее вы можете объяснить создание объекта с базовым исходным ресурсом, например:

 public class Main { public static void main(String args[]) { Socket s = new Socket(InetAddress.getByName("google.com"),80); s=null;   } } 

Затем вы можете объяснить, что это технически утечка памяти, но на самом деле утечка вызвана собственным кодом в JVM, который выделяет основные ресурсы, которые не были освобождены вашим кодом Java.

В конце дня, с современной JVM, вам нужно написать некоторый Java-код, который выделяет собственный ресурс вне обычной области осведомленности о JVM.

16
ответ дан deltamind106 13 окт. '17 в 22:09 2017-10-13 22:09

Вы можете создать утечку движущейся памяти, создав новый экземпляр класса в этом методе finalize. Бонусные очки, если финализатор создает несколько экземпляров. Здесь простая программа, которая утечки всей кучи за несколько секунд и несколько минут в зависимости от вашего размера кучи:

 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()); } } } 
16
ответ дан sethobrien 22 июля '11 в 11:05 2011-07-22 11:05

Каждый всегда забывает маршрут собственного кода. Здесь простая формула для утечки:

  • Объявить собственный метод.
  • В собственном методе вызовите malloc . Не вызывайте free .
  • Вызвать собственный метод.

Помните, что выделение памяти в собственном коде происходит из кучи JVM.

16
ответ дан Paul Morie 21 июля '11 в 23:52 2011-07-21 23:52

Недавно я столкнулся с более тонким видом утечки ресурсов. Мы открываем ресурсы через загрузчик классов getResourceAsStream, и оказалось, что обработчики входного потока не были закрыты.

Эм, можно сказать, какой идиот.

Ну, что делает интересным это: таким образом, вы можете пропустить кучу памяти лежащего в основе процесса, а не из кучи JVM.

Все, что вам нужно, это файл jar с файлом, внутри которого будет ссылаться код Java. Чем больше файл jar, тем быстрее выделяется более быстрая память.

Вы можете легко создать такую ​​банку со следующим классом:

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

Просто вставьте в файл BigJarCreator.java, скомпилируйте и запустите его из командной строки:

 javac BigJarCreator.java java -cp . BigJarCreator 

Et voilà: вы найдете архив jar в своем текущем рабочем каталоге с двумя файлами внутри.

Создайте второй класс:

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

Этот класс в основном ничего не делает, но создает неопубликованные объекты InputStream. Эти объекты будут собирать мусор немедленно и, таким образом, не вносить вклад в размер кучи. Для нашего примера важно загрузить существующий ресурс из файла jar, и размер имеет значение здесь!

Если вы сомневаетесь, попробуйте скомпилировать и запустить класс выше, но обязательно выберите подходящий размер кучи (2 МБ):

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

Здесь вы не столкнетесь с ошибкой OOM, так как ссылки не поддерживаются, приложение будет работать независимо от того, насколько большой вы выбрали Итерации в приведенном выше примере. Потребление памяти вашего процесса (видимое в верхнем (RES/RSS) или проводнике процессов) растет, если приложение не получает команду wait. В вышеприведенной настройке он выделяет около 150 МБ в памяти.

Если вы хотите, чтобы приложение играло безопасно, закройте входной поток прямо там, где он был создан:

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

и ваш процесс не будет превышать 35 МБ, независимо от количества итераций.

Довольно просто и удивительно.

15
ответ дан Jay 12 сент. '12 в 23:07 2012-09-12 23:07

Как и многие люди, утечки ресурсов довольно легко вызвать - например, примеры JDBC. Фактические утечки памяти немного сложнее - особенно если вы не полагаетесь на разбитые биты JVM, чтобы сделать это для вас...

Идеи создания объектов, которые имеют очень большой размер, а затем не могут получить к ним доступ, также не являются настоящими утечками памяти. Если ничто не может получить к нему доступ, тогда будет собран мусор, и если что-то сможет получить к нему доступ, то это не утечка...

Один из способов, который раньше работал, - и я не знаю, все еще ли это, - иметь трехмерную круговую цепочку. Так как в объекте A имеется ссылка на объект B, объект B имеет ссылку на объект C, а объект C имеет ссылку на объект A. GC был достаточно умен, чтобы знать, что две глубокие цепи - как в ↔ B - можно безопасно собрать, если A и B недоступны ничем другим, но не могут обрабатывать трехстороннюю цепочку...

14
ответ дан Graham 21 июля '11 в 20:55 2011-07-21 20:55

Я не думаю, что кто-то еще это сказал: вы можете воскресить объект, переопределив метод finalize() таким образом, чтобы finalize() хранит ссылку на это где-то. Сборщик мусора будет вызываться только один раз на объекте, после чего объект никогда не будет уничтожен.

14
ответ дан Ben 03 июля '11 в 20:36 2011-07-03 20:36

Нитки не собираются до тех пор, пока они не прекратятся. Они служат roots сборкой мусора. Они являются одним из немногих объектов, которые не будут восстановлены просто, забыв о них или очистив ссылки на них.

Рассмотрим: основной шаблон для завершения рабочего потока - это установить некоторую переменную условия, видимую нитью. Поток может периодически проверять переменную и использовать это как сигнал для завершения. Если переменная не объявлена ​​ volatile , тогда изменение в переменной может не отображаться нитью, поэтому она не будет знать, что она завершена. Или представьте, если некоторые потоки хотят обновить общий объект, но тупик при попытке заблокировать его.

Если у вас есть только несколько потоков, эти ошибки, вероятно, будут очевидны, потому что ваша программа перестанет работать должным образом. Если у вас есть пул потоков, который при необходимости создает больше потоков, то устаревшие/застрявшие потоки могут быть не замечены и будут накапливаться бесконечно, вызывая утечку памяти. Потоки, скорее всего, будут использовать другие данные в вашем приложении, а также предотвратят все, что они непосредственно ссылаются, из когда-либо собранных.

В качестве примера игрушки:

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

Вызовите System.gc() все, что вам нравится, но объект, переданный в leakMe , никогда не умрет.

(* изм *)

11
ответ дан Boann 31 авг. '13 в 8:10 2013-08-31 08:10

существует много разных ситуаций, в которых происходит утечка памяти. Одна из тех, с которыми я столкнулась, представляет собой карту, которую нельзя открывать и использовать в другом месте.

 public class ServiceFactory { private Map<String, Service> services; private static ServiceFactory singleton; private ServiceFactory() { services = new HashMap<String, Service>(); } public static synchronized ServiceFactory getDefault() { if (singleton == null) { singleton = new ServiceFactory(); } return singleton; } public void addService(String name, Service serv) { services.put(name, serv); } public void removeService(String name) { services.remove(name); } public Service getService(String name, Service serv) { return services.get(name); } // the problematic api, which expose the map. //and user can do quite a lot of thing from this api. //for example, create service reference and forget to dispose or set it null //in all this is a dangerous api, and should not expose public Map<String, Service> getAllServices() { return services; } } // resource class is a heavy class class Service { } 
11
ответ дан Ben Xu 10 июля '11 в 10:46 2011-07-10 10:46

Другой способ создать потенциально большие утечки памяти - хранить ссылки на Map.Entry<K,V> TreeMap .

Трудно понять, почему это применимо только к TreeMap , но, глядя на реализацию, причина может быть в следующем: TreeMap.Entry хранит ссылки на своих братьев и сестер, поэтому, если TreeMap готов к сбору, но некоторые другие классы содержат ссылка на любой из его Map.Entry , тогда вся карта будет сохранена в памяти.


Реальный сценарий:

Представьте, что у вас есть запрос TreeMap данных, который возвращает большую структуру данных TreeMap . Люди обычно используют TreeMap поскольку порядок вставки элементов сохраняется.

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

Если запрос вызывался много раз, и для каждого запроса (то есть для каждой возвращаемой Map ) вы сохраняете Entry где-нибудь, память постоянно будет расти.

Рассмотрим следующий класс-оболочку:

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

Заявка:

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

После каждого pseudoQueryDatabase() экземпляры map должны быть готовы к сбору, но этого не произойдет, поскольку хотя бы одна Entry хранится где-то еще.

В зависимости от настроек jvm , приложение может OutOfMemoryError на ранней стадии из-за OutOfMemoryError .

Из этого графика visualvm как растет память.

2019

ответ дан Marko Pacak 26 апр. '18 в 15:21 2018-04-26 15:21

Интервьюер, возможно, искал круглое справочное решение:

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

Это классическая проблема со ссылкой на сборщики мусора. Затем вы вежливо объясните, что JVM используют гораздо более сложный алгоритм, который не имеет этого ограничения.

-Wes Tarle

9
ответ дан Wesley Tarle 04 июля '11 в 18:06 2011-07-04 18:06
  • 1
  • 2

Другие вопросы по меткам или Задайте вопрос