Питання Створення витоків пам'яті з Java


Я просто мав інтерв'ю, і мене попросили створити витік пам'яті з Java. Зайве говорити, що я відчував себе досить німим, не маючи поняття про те, як навіть почати створювати його.

Що буде прикладом?


2641


походження


Я б сказав їм, що Java використовує збирач сміття, і попросіть їх трохи конкретизувати їх визначення "витоку пам'яті", пояснюючи це - заборони JVM помилок - Java не може витікати пам'ять цілком так само, як може C / C ++. Ви повинні мати посилання на об'єкт десь. - Darien
Я вважаю забавним, що за більшістю відповідей люди шукають цих краєвих випадків і хитрощів, і, здається, цілком не вистачає точки (IMO). Вони можуть просто показати код, який тримає непотрібні посилання на об'єкти, які ніколи не будуть використовуватись знову, і в той же час ніколи не випускати ці посилання; можна сказати, що ці випадки не є "справжніми" витоками пам'яті, тому що все ще є посилання на ці об'єкти навколо, але якщо програма ніколи не використовуватиме ці посилання, а також ніколи не скидатиме їх, це цілком еквівалентно (і так погано) "" справжня витік пам'яті ". - ehabkost
Чесно кажучи, я не можу повірити, що подібне питання, яке я запитав про "Go", отримано вниз до -1. Тут: stackoverflow.com/questions/4400311/...   По суті, витоки пам'яті, про які я говорив, - це ті, хто отримав +200 версій до OP, але я напав і ображений, щоб запитати, чи має "Го" ту ж саму проблему. Як-небудь я не впевнений, що все вікі-речі працюють так чудово. - SyntaxT3rr0r
@ SyntaxT3rr0r - відповідь darien не фанбуїзм. він явно визнав, що певні JVM можуть мати помилки, що означає, що пам'ять виходить. це відрізняється від самої мовної специфікації, яка дозволяє витоку пам'яті. - Peter Recore
@ehabkost: Ні, вони не еквівалентні. (1) Ви володієте здатністю відновити пам'ять, тоді як в "справжній витоку" ваша програма C / C ++ забуде діапазон, який було виділено, немає безпечного способу відновлення. (2) Ви можете дуже легко виявити проблему з профілюванням, тому що ви можете побачити, які об'єкти "роздути" включає. (3) "Істинна витока" - це однозначна помилка, а програма, яка тримає багато об'єктів, доки вона не закінчиться міг бути навмисною частиною того, як вона повинна працювати. - Darien


Відповіді:


Ось хороший спосіб створити справжній витік пам'яті (об'єкти, недоступні за допомогою коду, але все ще зберігаються в пам'яті) у чистому Java:

  1. Програма створює довготривалу гілку (або використовує пул потоків для витоку ще швидше).
  2. Потік завантажує клас за допомогою (за бажанням замовленого) ClassLoader.
  3. Клас виділяє велику частину пам'яті (наприклад, new byte[1000000]), зберігає сильне посилання на нього в статичному полі, а потім зберігає посилання на себе в ThreadLocal. Виділення додаткової пам'яті необов'язкове (достатньо тестування на прикладі класу), але це зробить витік роботу набагато швидшою.
  4. Потік очищає всі посилання на спеціальний клас або клас завантажувача, з якого він був завантажений.
  5. Повторити

Це працює тому, що ThreadLocal зберігає посилання на об'єкт, який зберігає посилання на свій клас, який у свою чергу зберігає посилання на свій ClassLoader. Клас Loader, у свою чергу, зберігає посилання на всі Класи, які він завантажив.

(Це було гірше в багатьох реалізаціях JVM, особливо до Java 7, оскільки Classes і ClassLoaders були виділені прямо в Permgen і ніколи не були GC'd зовсім. Однак незалежно від того, як JVM обробляє розвантаження класу, ThreadLocal все одно перешкоджає Клас об'єкт відновлюється.)

Варіація цього шаблону полягає в тому, чому консолі додатків (наприклад, Tomcat) можуть витікати пам'ять, як сито, якщо ви часто передивляєте програми, які використовуються ThreadLocals будь-яким способом. (Оскільки контейнер додатків використовує Threads, як описано, і щоразу, коли ви перекладаєте програму, використовується новий ClassLoader.)

Оновити: Оскільки багато людей продовжують питати про це, Ось приклад коду, який показує цю поведінку в дії.


1935



Класифікатори класу +1 класу - це найчастіше болісні витоки пам'яті в світі JEE, часто викликані сторонніми бібліотеками, які перетворюють дані (BeanUtils, XML / JSON кодеки). Це може статися, коли lib завантажується за межами завантажувача кореневого каталогу вашого додатка, але містить посилання на ваші класи (наприклад, за допомогою кешування). Коли ви не розташуєте / перенаправляєте додаток, JVM не може збирати завантажувач класу програми (і, отже, всі завантажені ним класи), так що при повторному розгортанні сервер додатків врешті-решт забороняється. Якщо вам пощастить, ви отримаєте ключ за допомогою ClassCastException z.x.y.Abc не можна відкинути на z.x.y.Abc - earcam
Tomcat використовує трюки та nils ВСІ статичні змінні у ВСІх завантажених класах, хоча tomcat має багато ідентифікаторів і погане кодування, хоча (потрібно трохи часу і виправлення), а також всі ConclusionLinkedQueue, як кеш для внутрішніх (малих) об'єктів, настільки малий, що навіть ConcurrentLinkedQueue.Node займає більше пам'яті. - bestsss
+1: витоки Classloader - це кошмар. Я витрачав тижні, намагаючись розібрати їх. Сумно, що, як сказав @earcam, вони в основному викликані сторонніми ліберами, а також більшість профайлерів не можуть виявити ці витоки. У цьому блозі про витоки Classloader є чітке та чітке пояснення. blogs.oracle.com/fkieviet/entry/... - Adrian M
@ Ніколас: Ви впевнені? JRockit робить об'єкти класу GC за замовчуванням, а HotSpot - не, але AFAIK JRockit все одно не може GC класу або ClassLoader, на який посилається ThreadLocal. - Daniel Pryden
Tomcat намагатиметься виявити ці витоки для вас і попередити про них: wiki.apache.org/tomcat/MemoryLeakProtection. Найновіша версія може іноді навіть виправити витоку для вас. - Matthijs Bierman


Статичне поле відстеження об'єкта посилання [esp остаточне поле]

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

Дзвінок String.intern() на довгій String

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

(Незакриті) відкриті потоки (файл, мережа тощо)

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

Незамкнені з'єднання

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

Області, недоступні від збирача сміття JVM, наприклад, пам'ять, виділена за допомогою нативних методів

У веб-програмах деякі об'єкти зберігаються в області застосування до тих пір, поки програма явно не зупиняється чи не видаляється.

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

Неправильні або невідповідні варіанти JVM, такі як noclassgc варіант на IBM JDK, який запобігає невикористанню колекції сміття класу

Побачити IBM jdk settings.


1062



Я б не погодився, що атрибути контексту та сеансу є "витоками". Вони просто довгоживучі змінні. І статичне кінцеве поле - більш-менш просто константа. Може бути, великих констант слід уникати, але я не думаю, що це справедливо, щоб назвати це витік пам'яті. - Ian McLaird
(Незакриті) відкриті потоки (файл, мережа тощо), не протікає реально, під час остаточної розробки (який буде після наступного циклу ГХ) close () буде заплановано (close() зазвичай не викликається в потоці завершувача, оскільки може бути блокованою операцією). Це погана практика не закривати, але це не викликає витоку. Незакрита java.sql.Connection однакова. - bestsss
У найбільш розсудливих JVM, здається, що клас String має лише слабку посилання на її intern хешбайтовий вміст. Як такий, це є сміття збирається належним чином, а не витоку. (але IANAJP) mindprod.com/jgloss/interned.html#GC - Matt B.
Як статична поле збереження об'єкта [esp final field] - це витік пам'яті? - Kanagavelu Sugumar
@chao True. Мова, з якою я зіткнувся, - це не проблеми з пам'яттю, що витікає з потоків. Проблема в тому, що їх не вистачає пам'яті. Ви можете витоку багато ручок, але у них ще багато пам'яті. Смітник може вирішити не турбувати робити повну колекцію, оскільки вона все ще має багато пам'яті. Це означає, що фіналізатор не викликається, тому ви запускаєте ручки. Проблема в тому, що finalizers (зазвичай) будуть запускатися, перш ніж ви вичерпаєте пам'ять про витоку потоків, але це може не отримати виклик, перш ніж закінчиться щось інше, ніж пам'ять. - Patrick M


Проста річ - використовувати HashSet з неправильним (або неіснуючим) hashCode() або equals(), а потім продовжуйте додавати "дублікати". Замість того, щоб ігнорувати дублікати, як це слід, встановлений буде тільки коли-небудь зростати, і ви не зможете їх видалити.

Якщо ви хочете, щоб ці погані ключі / елементи висіли навколо вас, ви можете використовувати статичне поле, як

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



Власне, ви можете видалити елементи з HashSet, навіть якщо клас елемента отримує hashCode і рівний невірному; просто отримайте ітератор для набору і скористайтеся його методом видалення, оскільки ітератор насправді працює на основних записах, а не на елементах. (Зверніть увагу, що невиконаний hashCode / рівних є ні достатньо, щоб викликати витік; за замовчуванням виконується простий ідентифікатор об'єкта, і ви можете отримати елементи і видалити їх нормально.) - Donal Fellows
@Донал, що я намагаюся сказати, я гадаю, я не згоден з вашим визначенням витоку пам'яті. Я б розглянув (для продовження аналогії) вашу техніку видалення ітератора як капелюшку під витік; витік все ще існує незалежно від крапельниці. - corsiKa
Я згоден, це так ні пам'ять "витік", оскільки ви можете просто видалити посилання на збіг і почекати, коли GC вдасться, і presto! пам'ять сходить. - Mehrdad
@ SyntaxT3rr0r, я інтерпретував ваше запитання, як запитав, чи є щось на мові, яка, природно, призводить до витоків пам'яті. Відповідь - НІ. Ці питання запитують, чи можна придумати ситуацію, щоб створити щось на зразок витоку пам'яті. Жоден з цих прикладів не витоку пам'яті таким чином, щоб програміст C / C ++ це зрозумів. - Peter Lawrey
@Петер Лоурі: також, що ви думаєте про це: "У мові C немає нічого, який, природно, потрапляє до витоку пам'яті, якщо ви не забудете вручну видалити виділену пам'ять". Як це буде для інтелектуальної нечесності? У всякому разі, я втомився: ви можете мати останнє слово. - SyntaxT3rr0r


Нижче буде невидимий випадок, коли Java витік, крім стандартного випадку забутих слухачів, статичних посилань, фальшивих / модифікованих ключів в хешпах або просто заклинаннях, які не можуть припинити життєвий цикл.

  • File.deleteOnExit()- завжди витікає струна, якщо рядок є підрядкою, витік ще гірший (також витік базовий символ [] - в підменю Java 7 також копіює char[], тому пізніше не застосовується; @ Даніель, немає потреби в голосуванні, хоча.

Я зосереджуся на потоках, щоб показати небезпеку неврегульованих ниток в основному, не хочуть навіть натискати на гойдалки.

  • Runtime.addShutdownHook і не видаляти ..., а потім навіть з removeShutdownHook через помилку в класі ThreadGroup відносно незміненого потоку він не може зібрати, фактично витік ThreadGroup. JGroup має витік у GossipRouter.

  • Створення, але не запуск, a Thread йде в ту ж категорію, що й вище.

  • Створення потоку успадковує ContextClassLoader і AccessControlContext, плюс ThreadGroup і будь-який InheritedThreadLocal, всі ці посилання є потенційними витоками разом з усіма класами, завантаженими завантажувачем класів та всіма статичними посиланнями, і ja-ja. Ефект особливо помітний у всій системі j.u.c.Executor, яка має надзвичайно просту функцію ThreadFactory Інтерфейс, проте більшість розробників не мають жодної підказки про загрозливу небезпеку. Також багато бібліотек запускають теми за запитом (занадто багато популярних бібліотек промисловості).

  • ThreadLocal кеш-пам'ять; в багатьох випадках вони є зла. Я впевнений, що всі бачили дуже багато простих кешей на основі ThreadLocal, а також погані новини: якщо потік продовжує йти більше, ніж очікувалося, життя контексту ClassLoader, це чиста приємна невелика кількість витоків. Не використовуйте кеш ThreadLocal, якщо це не потрібно.

  • Дзвінок ThreadGroup.destroy() коли ThreadGroup не має самих ниток, але він все ще зберігає дитячі ThreadGroups. Погана витока, яка перешкоджає видаленню ThreadGroup з батьків, але всі діти стають непереважними.

  • Використання WeakHashMap та значення (in) безпосередньо посилаються на ключ. Це важко знайти без скидання купи. Це стосується всіх розширених Weak/SoftReference що може тримати тверду базу назад до охоронюваного об'єкта.

  • Використовуючи java.net.URL з протоколом HTTP (S) і завантаження ресурсу з (!). Ця особлива особливість KeepAliveCache створює нову гілку в системі ThreadGroup, яка витікає з контекстного завантажувача контексту поточного потоку. Потік створюється за першим запитом, коли жодна жива течія не існує, тому вам може стати щасливим чи просто витік. Витік вже фіксується в Java 7, і код, який створює потоку, правильно видаляє контекстний навантажувач класів. Є ще кілька випадків (як ImageFetcher, також фіксується) створення подібних потоків.

  • Використовуючи InflaterInputStream проходження new java.util.zip.Inflater() в конструкторе (PNGImageDecoder наприклад) і не дзвонити end() від надувного пристрою. Ну, якщо ви перейдете в конструктор просто new, немає шансів ... І так, зателефонувавши close() на потоці не закриває напівавтомат, якщо його передано вручну як параметр конструктора. Це не є справжнім витоком, оскільки він буде випущений фіналістом ... коли він вважатиме це необхідним. До цього моменту він споживає рідну пам'ять, так що це може призвести до того, що Linux oom_killer вбиває процес безкарно. Головне питання полягає в тому, що завершення роботи в Java дуже ненадійне, і G1 зробив це гіршим до 7.0.2. Мораль історії: випуск рідних ресурсів якомога швидше; фіналіст просто надто бідний.

  • Той самий випадок з java.util.zip.Deflater. Цей параметр набагато гірший, оскільки Deflater - це пам'ять, яка голіла в Java, тобто завжди використовує 15 біт (макс.) І 8 рівнів пам'яті (9 є максимальною), що виділяє декілька сотень Кб власної пам'яті. На щастя, Deflater не широко використовується, і, на мої знання, JDK не містить жодних зловживань. Завжди дзвоніть end() якщо ви вручну створите a Deflater або Inflater. Найкраща частина останніх двох: ви не можете знайти їх за допомогою звичайних інструментів профілювання.

(Я можу додати трохи більше шансів на час, який я зіткнувся на вимогу.)

Удачі і залишитися в безпеці; витік - це зло!


228



Creating but not starting a Thread... Yikes, я був сильно вкушений цим цілим кілька століть тому! (Java 1.3) - leonbloy
@leonbloy, перш ніж це було ще гірше, коли нитка була додана прямо в групу різьблень, не починаючи означала дуже жорстку витік. Це не просто збільшує unstarted Рахуйте, але це запобігає руйнуванню групи ниток (менш злі, але все-таки витік) - bestsss
Дякую! "Виклик ThreadGroup.destroy() коли ThreadGroup не має самих ниток ... " це неймовірно витончена помилка; Я ганяв це протягом декількох годин, збився з ладу, тому що перерахування нитки в моєму контролі GUI нічого не виявило, але група потоків і, мабуть, принаймні одна дитяча група не пішла. - Lawrence Dol
@ bestsss: Мені цікаво, чому б ви хочете видалити гак з вимкненням, з огляду на те, що він працює, ну, вимкнення JVM? - Lawrence Dol


Більшість прикладів тут "занадто складні". Вони - крайові справи. З цими прикладами програміст зробив помилку (наприклад, не перевизначаючи equals / hashcode), або був вкусив у кутку випадку JVM / JAVA (завантаження класу з статичним ...). Я думаю, що це не той тип прикладу, який хотів інтерв'юер або навіть найпоширеніший випадок.

Але існують справді простіші випадки витоків пам'яті. Смітник збирає тільки те, що більше не згадується. Ми, як розробники Java, не дбаємо про пам'ять. Ми виділяємо його, коли це потрібно, і давайте його автоматично звільняємо. Чудово

Але будь-який довготривалий додаток, як правило, має спільний стан. Це може бути будь-що, статика, синглетони ... Часто нетривіальні додатки, як правило, роблять складні графіки об'єктів. Просто забуваючи встановити посилання на нуль або частіше забувши про видалення одного об'єкта з колекції, достатньо для витоку пам'яті.

Звичайно, всі сорти слухачів (наприклад, слухачів інтерфейсу), кеш-пам'ять або будь-яка довгоживуча спільна стан, як правило, призводять до витоку пам'яті, якщо вона не обробляється належним чином. Що слід розуміти, це те, що це не Java-кут, або проблема з збирачем сміття. Це проблема дизайну. Ми проектуємо, що ми додаємо слухача до довгоживучого об'єкта, але ми не видаляємо слухача, коли більше не потрібні. Ми кешувати об'єкти, але у нас немає стратегії видалення їх з кеша.

Можливо, у нас є складний граф, який зберігає попередній стан, який потрібний для обчислення. Але попередній стан сам по собі пов'язаний з державою і т. Д.

Як нам потрібно закрити з'єднання або файли SQL. Нам потрібно встановити відповідні посилання на нульові та видалити елементи з колекції. Ми матимемо належні стратегії кешування (максимальний об'єм пам'яті, кількість елементів або таймери). Всі об'єкти, які дозволяють отримувати сповіщення про слухача, повинні містити як метод addListener, так і removeListener. І коли ці сповіщувачі більше не використовуються, вони повинні очистити свій список слухачів.

Витік пам'яті насправді дійсно можливий і цілком передбачуваний. Не потрібно спеціальних мовних функцій або кутових справ. Витоки пам'яті - або індикатор, який може бути відсутнім або навіть проблеми з дизайном.


150



Я вважаю це смішним, що за іншими відповідями люди шукають цих краєвидів і трюків, і, здається, цілком втрачають точку. Вони можуть просто показувати код, який тримає непотрібні посилання на об'єкти, які ніколи не використовуватимуть, і ніколи не видаляти ці посилання; можна сказати, що ці випадки не є "справжніми" витоками пам'яті, тому що все ще є посилання на ці об'єкти навколо, але якщо програма ніколи не використовуватиме ці посилання, а також ніколи не скидатиме їх, це цілком еквівалентно (і так погано) "" справжня витік пам'яті ". - ehabkost
@Nicolas Bousquet: "Витік пам'яті дійсно тріально можливий"   Дуже дякую. + 15 перевищ. Приємно Я заговорив тут за те, що зазначив цей факт, як і питання про мову Go: stackoverflow.com/questions/4400311   Це питання все ще має негативні падіння :( - SyntaxT3rr0r
GC в Java та .NET в деякому сенсі визначається графіком припущення об'єктів, що містять посилання на інші об'єкти, такий самий, як графік об'єктів, які "піклуються" про інші об'єкти. Насправді, можливо, що ребра можуть існувати в довідковому графіку, які не відображають "дбайливих" відносин, і об'єкт може піклуватися про існування іншого об'єкта, навіть якщо немає прямого або непрямого посилання (навіть при використанні WeakReference) існує від одного до іншого. Якщо посилання на об'єкт був запасним, це може бути корисним показник "турбота про ціль" ... - supercat
... і мати систему повідомлення (за допомогою засобів, подібних до PhantomReference), якщо було встановлено, що об'єкта не було нікого, хто піклувався про це. WeakReference наближається дещо ближче, але необхідно перетворити на міцне посилання, перш ніж він може бути використаний; якщо цикл GC відбувається під час існування сильної посилання, ціль буде вважатись корисною. - supercat
Це, на мій погляд, правильна відповідь. Ми написали симуляцію років тому. Так чи інакше ми випадково зв'язали попередній стан із поточним станом, що створює витік пам'яті. Через кінцевий термін ми ніколи не вирішили витоку пам'яті, але зробили її "функцією", документуючи її. - nalply


Відповідь повністю залежить від того, що інтерв'юер думав, що вони запитують.

Чи можна на практиці зробити витік Java? Звичайно, це є, і в інших відповідях є багато прикладів.

Але існує декілька мета-запитань, які, можливо, запитувалися?

  • Теоретично "ідеальна" реалізація Java уразлива для витоків?
  • Чи знає кандидат різницю між теорією та реальністю?
  • Чи знає кандидат, як працює сміттєзвалище?
  • Або як сміття збирається працювати в ідеальному випадку?
  • Чи знають вони, що вони можуть викликати інші мови через рідні інтерфейси?
  • Чи знають вони, що витік пам'яті на інших мовах?
  • Чи навіть кандидат навіть знає, що таке управління пам'яттю, і що відбувається за сценою в Java?

Я читаю ваш мета-запитання як "Яка відповідь я міг би використати в ситуації, що склалася в цьому інтерв'ю". І, отже, я збираюся зосередитись на навичках інтерв'ю, а не на Java. Я вважаю, що ви, швидше за все, повторюєте ситуацію, коли ви не знаєте відповіді на запитання в інтерв'ю, ніж ви повинні знати про те, як зробити витік Java. Отже, сподіваюся, це допоможе.

Одна з найважливіших навичок, які ви можете розробити для опитування, - навчитися активно слухати питання та співпрацювати з інтерв'юер, щоб витягти їх намір. Це не тільки дасть вам відповідь на своє запитання так, як вони хочуть, але також показує, що у вас є деякі життєво важливі комунікативні навички. І коли справа доходить до вибору між багатьма такими ж талановитими розробниками, я наймаю того, хто слухає, думає і розуміє, перш ніж вони реагують кожного разу.


133



Кожного разу, коли я запитую це питання, я шукаю досить просту відповідь - продовжуйте наростати чергу, не нарешті закривайте db тощо, а не нечіткі дані класу навантажувача / потоку, мабуть, вони розуміють, що gc може і не може зробити для вас. Залежить від роботи, в якій ви проводите співбесіду, на мій погляд. - DaveC
Будь ласка, подивіться на моє запитання, дякую stackoverflow.com/questions/31108772/... - Daniel Newtown


Нижче поданий досить безглуздий приклад, якщо ви не розумієте JDBC. Або, як мінімум, JDBC очікує закриття розробника Connection, Statement і ResultSet випадки, перш ніж відкидати їх або втратити посилання на них, а не покладатися на реалізацію 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);
   }
}

Проблема з вищенаведеним полягає в тому, що Connection об'єкт не закритий, і, отже, фізичний зв'язок залишатиметься відкритим, поки збирач сміття не наблизиться і побачить, що це недосяжне. GC буде викликати finalize метод, але є драйвери JDBC, які не реалізують finalize, принаймні не таким самим чином, що Connection.close реалізована. Отримана поведінка полягає в тому, що поки пам'ять буде відновлена ​​через збирання недоступних об'єктів, ресурси (включаючи пам'ять), пов'язані з Connection об'єкт може просто не бути відновлений.

У такій події, де Connectionс finalize Метод не очищає все, фактично можна виявити, що фізичне підключення до сервера баз даних триватиме кілька циклів збору сміття, поки сервер бази даних зрештою не з'ясовує, що з'єднання не є живим (якщо він є), і його слід закрити.

Навіть якщо б JDBC драйвер був реалізований finalize, можна під час завершення викинути винятки. Результуюча поведінка полягає в тому, що будь-яка пам'ять, пов'язана з поточним "незмінним" об'єктом, не буде відновлена, як finalize гарантовано буде викликати лише один раз.

Вищезгаданий сценарій виявлення винятків під час завершення об'єкта пов'язаний з ще одним сценарієм, який може призвести до витікання пам'яті - воскресіння об'єкта. Воскресіння об'єкта часто виконується навмисне, створюючи сильне посилання на об'єкт, що його готує, з іншого об'єкта. При зловживанні об'єктом воскресіння це призведе до витоку пам'яті у поєднанні з іншими джерелами витоків пам'яті.

Існує багато інших прикладів, які ви можете викликати, як

  • Управління a List екземпляр, де ви тільки додаєте до списку, а не видаляєте з нього (хоча ви повинні позбутися елементів, які вам більше не потрібні), або
  • Відкриття SocketS або Files, але не закриваючи їх, коли вони більше не потрібні (подібно до наведеного вище прикладу, що включає Connection клас)
  • Не вивантажуючи Singletons при зведенні програми Java EE. Очевидно, що Class Loader, який завантажив клас одитонів, зберігатиме посилання на клас, і тому екземпляр singleton ніколи не буде зібраний. Коли новий екземпляр програми розгортається, звичайно створюється новий завантажувач класів, і колишній завантажувач класів продовжує існувати через одиночну.

113



Ви досягнете максимальної ліміт відкритого з'єднання, перш ніж натискати межі пам'яті зазвичай. Не питай мене, чому я знаю ... - Hardwareguy
Драйвер JDBC Oracle славиться тим, що робить це. - chotchki
@Hardwareguy Я доторкнувся до лімітів зв'язку баз даних SQL, доки я не став Connection.close в остаточний блок всіх моїх викликів SQL. Для додаткової забави я зателефонував деяким тривалим операційним потокам Oracle, які потребували блокування на стороні Java, щоб запобігти надто багато дзвінків до бази даних. - Michael Shopsin
@Hardwareguy Це цікаво, але насправді не обов'язково, що фактичні межі зв'язку будуть попаплені для всіх середовищ. Наприклад, для програми, розгорнутої на сервері 11g weblogic, я бачив витоки з'єднання у великих масштабах. Але завдяки можливості збирання витікаючих з'єднань з'єднання з базами даних залишалися доступними при витоку пам'яті. Я не впевнений у всіх середовищах. - Aseem Bansal
У мій досвід ви отримуєте витік пам'яті, навіть якщо ви закриєте з'єднання. Вам спочатку потрібно закрити ResultSet і PreparedStatement. Якщо сервер, який неодноразово стикався після декількох годин чи навіть днів, працював нормально, через OutOfMemoryErrors, поки я не почав це робити. - Bjørn Stenfeldt


Напевно, один з найпростіших прикладів потенційної втрати пам'яті, і як уникнути цього, є реалізація ArrayList.remove (int):

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

Якщо б ви самі реалізовували це, чи могли б ви очистити елемент масиву, який більше не використовується (elementData[--size] = null)? Це посилання може зберегти величезний об'єкт живий ...


102



'elementData [- size] = null;' робить очищення, я думаю ... - maniek
І де витік пам'яті тут? - rds
@maniek: Я не мав на увазі означати, що цей код демонструє витік пам'яті. Я цитував його, щоб показати, що іноді необов'язковий код необхідний для уникнення випадкового затримання об'єкта. - meriton
Що таке RangeCheck (індекс); ? - Koray Tugay
Джошуа Блох представив цей приклад у Ефективній Java, демонструючи просту реалізацію Stacks. Дуже хороша відповідь. - rents


Кожного разу, коли ви тримаєте довідники навколо об'єктів, які вам більше не потрібні, у вас є витік пам'яті. Побачити Обробка витоків пам'яті в програмах Java для прикладів того, як витік пам'яті проявляється в Java і що ви можете зробити про це.


63



Я не вірю, що це "витік". Це помилка, і це за проектом програми та мови. Витік буде об'єктом, що висить навколо без будь-які посилання на це. - Mehrdad
@ Меджрад: Це лише одне вузьке визначення, яке не повністю застосовується до всіх мов. Я б це стверджував будь-який Витік пам'яті - це помилка, спричинена поганим дизайном програми. - 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.  Я не бачу, як ви робите такий висновок. Існує менше способи створення витоків пам'яті в Java за будь-яким визначенням. Це, безумовно, дійсне питання. - Bill the Lizard
@ 31eee384: якщо ваша програма зберігає об'єкти в пам'яті, які він ніколи не може використовувати, то технічно це витік пам'яті. Той факт, що у вас є більші проблеми, насправді не змінює цього. - Bill the Lizard
@ 31eee384: Якщо ви знаєте, що це не буде, тоді це не може бути. Програма, як написано, ніколи не отримає доступ до даних. - Bill the Lizard


Ви можете зробити витік пам'яті sun.misc.Unsafe клас Фактично цей клас обслуговування використовується в різних стандартних класах (наприклад, в java.nio класи) Ви не можете безпосередньо створювати екземпляр цього класу, але ви можете використовуйте відображення для цього.

Код не збирається в Eclipse IDE - компілюйте його за допомогою команди javac (під час складання ви отримаєте попередження)

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



Виділена пам'ять невидима для збирача сміття - stemm
Виділена пам'ять також не належить до Java. - bestsss
Це сонце / оракул jvm специфічний? Наприклад, чи буде ця робота на IBM? - Berlin Brown
Пам'ять, безумовно, "належить до Java", принаймні в тому сенсі, що i) її недоступне комусь іншому, ii) коли прикладна програма Java виходить із системи, вона буде повернута до системи. Це просто поза межами JVM. - Michael Anderson
Вона буде вбудована в Eclipse (принаймні в останніх версіях), але вам потрібно буде змінити налаштування компілятора: у вікні> Параметри> Java> Компілятор> Помилки / Попередження> Не встановлений та обмежений API встановлено Заборонена довідка (правила доступу) до "Попередження" ". - Michael Anderson


Я можу скопіювати свою відповідь звідси: Найпростіший спосіб викликати витік пам'яті в Java?

"Витік пам'яті в комп'ютерній техніці (або витік у цьому контексті) виникає, коли комп'ютерна програма споживає пам'ять, але не може відновити її в операційну систему". (Вікіпедія)

Легка відповідь: Ви не можете. Java здійснює автоматичне керування пам'яттю і визволяє ресурси, які вам не потрібні. Ви не можете зупинити це. Вона завжди зможе випустити ресурси. У програмах з керуванням вручну це відрізняється. Ви не можете отримати деяку пам'ять в C за допомогою malloc (). Щоб звільнити пам'ять, потрібен покажчик, який повернув malloc, і виклик безкоштовно () на нього. Але якщо у вас більше немає покажчика (перезаписаний або перевищений термін служби), то, на жаль, на жаль, ви не можете звільнити цю пам'ять, і, таким чином, у вас є витік пам'яті.

Всі інші відповіді до цих пір в моєму визначенні не дуже витік пам'яті. Всі вони прагнуть наповнювати пам'ять безнадійним матеріалом реально швидко. Але в будь-який час ви все-таки можете вилучити об'єкти, створені вами, і тим самим звільнивши пам'ять -> NO LEAK. відповідь Аконрада приходить досить близько, хоча, як я повинен визнати, оскільки його рішення ефективно, щоб просто "збити" смітник, змусивши його в нескінченному циклі).

Довга відповідь: Ви можете отримати витік пам'яті, написавши бібліотеку для Java за допомогою JNI, яка може мати ручне керування пам'яттю і, таким чином, мати витік пам'яті. Якщо ви називаєте цю бібліотеку, ваш процес Java буде витікати з пам'яті. Або у вас можуть бути помилки в JVM, так що JVM втрачає пам'ять. У JVM, ймовірно, є помилки, можуть навіть бути деякі відомі, оскільки збирання сміття не настільки тривіальне, але потім воно все ще є помилкою. За конструкцією це неможливо. Ви можете запитати який-небудь Java-код, який виникає внаслідок такої помилки. На жаль, я не знаю, і в будь-якому випадку це може не бути помилкою в наступній версії Java.


37



Це дуже обмежене (і не дуже корисне) визначення витоків пам'яті. Єдине визначення, яке має сенс для практичних цілей, - "витік пам'яті - це будь-яка умова, при якій програма продовжує зберігати пам'ять, виділену після того, як дані, які вона містить, більше не потрібні". - Mason Wheeler
Вказана відповідь Акоронда була вилучена? - Tomáš Zato
@ TomášZato: Ні, це не так. Я звернув посилання вище, щоб зв'язати його зараз, щоб ви могли легко його знайти. - yankee
Що таке об'єкт воскресіння? Скільки разів викликається деструктор? Як ці питання спростовують цю відповідь? - autistic