Чому об’єкти Java не видаляються відразу після того, як на них більше не посилаються?


77

У Java, як тільки на об'єкт більше немає посилань, він стає придатним для видалення, але JVM вирішує, коли об'єкт буде фактично видалений. Щоб використовувати термінологію Objective-C, всі посилання на Java по суті є "сильними". Однак у Objective-C, якщо на об'єкт більше немає чітких посилань, об'єкт видаляється негайно. Чому це не так у Java?


46
Вам не варто хвилюватись, коли об’єкти Java фактично видаляються. Це деталь реалізації.
Василь Старинкевич

154
@BasileStarynkevitch Вам слід абсолютно піклуватися про те, як працює ваша система / платформа. Задавання питань "як" і "чому" - це один з найкращих способів стати кращим програмістом (і, загалом кажучи, розумнішою людиною).
Артур Бієсядовський

6
Що робить ціль C, коли є кругові посилання? Я припускаю, що це просто їх витікає?
Мехрдад

45
@ArturBiesiadowksi: Ні, специфікація Java не вказує, коли об’єкт буде видалено (і так само для R5RS ). Ви могли і, ймовірно, повинні розробити свою програму Java так, якби цього видалення ніколи не відбулося (а для короткочасних процесів, таких як Java привіт, справді цього не відбувається). Вас може хвилювати набір живих об’єктів (або споживання пам’яті), що вже інша історія.
Василь Старинкевич

28
Одного разу новачок сказав господареві: "У мене є рішення нашої проблеми з розподілом. Ми дамо кожному розподілу опорний підрахунок, і коли він досягне нуля, ми можемо видалити об'єкт". Майстер відповів: "Одного разу послушник сказав господареві:" У мене є рішення ...
Ерік Ліпперт

Відповіді:


79

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

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

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


58
Дружина програміста відправляє його в супермаркет. Вона каже йому: "Купіть буханець хліба, і якщо ви побачите кілька яєць, візьміть десяток". Пізніше програміст повертається з десятком хлібів під пахвою.
Ніл

7
Я пропоную зазначити, що час нового покоління gc, як правило, пропорційний кількості живих об'єктів, тому наявність більшої кількості видалених об'єктів означає, що їх вартість взагалі не виплачується. Видалити так само просто, як гортати покажчик простору вцілілого і, за бажанням, занулювати весь простір пам’яті в один великий мемсет (не впевнений, чи це зроблено в кінці gc чи амортизовано під час виділення тлабів чи самих об’єктів у поточних jvms)
Артур Бієсядовський

64
@Neil, чи не має бути 13 хлібів?
JAD

67
"Вимкнено однією помилкою на проході 7"
joeytwiddle

13
@JAD Я б сказав 13, але більшість не мають цього. ;)
Ніл

86

Оскільки правильно знати щось на це більше не посилається, непросто. Навіть близько до легкого.

Що робити, якщо у вас два об'єкти, що посилаються один на одного? Чи залишаються вони назавжди? Розширивши цей напрямок мислення до вирішення будь-якої довільної структури даних, і незабаром ви побачите, чому СВМ або інші збирачі сміття змушені застосовувати набагато складніші методи визначення того, що ще потрібно і що може йти.


7
Або ви можете скористатися підходом Python, коли ви максимально використовуєте перерахунок, звертаючись до GC, коли очікуєте, що в ньому виникає кругова залежність, що просочується пам'яттю. Я не бачу, чому вони не могли мати перерахунок коштів крім GC?
Мехрдад

27
@Mehrdad Вони могли. Але, напевно, було б повільніше. Ніщо не заважає вам реалізувати це, але не сподівайтесь перемогти будь-яку з GC в Hotspot або OpenJ9.
Йозеф

21
@ jpmc26, оскільки якщо ви видалите об'єкти, як тільки вони більше не використовуються, велика ймовірність, ви видалите їх у ситуації високого навантаження, що ще більше збільшує навантаження. GC може працювати, коли менше навантаження. Підрахунок посилань сам по собі є невеликою накладною витратою для кожної довідки. Крім того, за допомогою GC ви часто можете відкинути велику частину пам'яті без посилань без обробки окремих об'єктів.
Йозеф

33
@Josef: правильний підрахунок посилань також не безкоштовний; Оновлення еталонного підрахунку вимагає атомних приростів / декрементів, що надзвичайно дорого коштує , особливо в сучасних багатоядерних архітектурах. У CPython це не велика проблема (CPython самостійно надзвичайно повільний, а GIL обмежує свою багатопотоковість одноядерними рівнями), але на більш швидкій мові, яка також підтримує паралелізм, це може бути проблемою. Це не шанс, що PyPy позбудеться підрахунку посилань повністю і просто використовує GC.
Маттео Італія

10
@Mehrdad, коли ви реалізуєте ваш підрахунок посилань GC для Java, я з радістю перевіряю його, щоб знайти випадок, коли він працює гірше, ніж будь-яка інша реалізація GC.
Йозеф

45

AFAIK, специфікація JVM (написана англійською мовою) не вказує, коли саме об'єкт (або значення) слід видалити, і залишає це для реалізації (подібно до R5RS ). Це якось вимагає або пропонує сміттєзбірник, але деталі залишає для реалізації. І так само для специфікації Java.

Пам’ятайте, що мови програмування - це специфікації ( синтаксису , семантики тощо), а не реалізація програмного забезпечення. Мова на зразок Java (або її JVM) має багато реалізацій. Його специфікація публікується , завантажується (щоб ви могли вивчити її) та написана англійською мовою. §2.5.3 Купи специфікацій JVM згадують сміттєзбірник:

Зберігання купи об’єктів відновлюється системою автоматичного управління зберіганням (відома як сміттєзбірник); об'єкти ніколи прямо не розміщуються. Віртуальна машина Java не передбачає конкретного типу автоматичного управління зберіганням

(акцент мій; доопрацювання BTW згадується в §12.6 специфікації Java, а модель пам'яті - в §17.4 специфікації Java)

Тож (на Java) вам не варто піклуватися про те, коли об’єкт видаляється , і ви можете кодувати так, як якщо б це не сталося (міркуючи в абстракції, де ви ігноруєте це). Звичайно, вам потрібно подбати про споживання пам’яті та набір живих об’єктів, це вже інше питання. У кількох простих випадках (подумайте про програму "привіт світ") ви зможете довести - або переконати себе - що виділена пам'ять досить мала (наприклад, менше, ніж гігабайт), і тоді вам зовсім не байдуже видалення окремих об'єктів. У більшості випадків можна переконати себе, що живі об’єкти(або доступні, які є надмірним - легше міркувати про - живих) ніколи не перевищують розумного ліміту (і тоді ви покладаєтесь на GC, але вам все одно, як і коли відбувається сміття). Читайте про складність простору .

Я здогадуюсь, що у кількох реалізаціях JVM, які запускають короткочасну програму Java, як привіт, світ, збирач сміття взагалі не спрацьовує, і видалення не відбувається. AFAIU, така поведінка відповідає численним специфікаціям Java.

Більшість реалізацій JVM використовують поколінні методи копіювання (принаймні, для більшості об’єктів Java, тих, що не використовують фіналізацію або слабкі посилання ; і доопрацювання не гарантується, що відбудеться за короткий час і може бути відкладено, тому це просто корисна функція, що ваш код не повинен багато в чому залежать від), в якому поняття про видалення окремого об'єкта не має жодного сенсу (оскільки великий блок пам'яті, що містить зони пам'яті для багатьох об'єктів, можливо, відразу кілька мегабайт, вивільняється одразу).

Якщо специфікація JVM вимагає видалення кожного об'єкта якнайшвидше (або просто обмеження для видалення об'єкта), ефективні поколінні методи GC були б заборонені, і дизайнери Java та JVM з розумом уникали цього.

До речі, можливо, наївний JVM, який ніколи не видаляє об'єкти і не звільняє пам'ять, може відповідати специфікаціям (літера, а не дух) і, безумовно, здатний на практиці запустити привіт у світі (зауважте, що більшість крихітні та недовговічні програми Java, мабуть, не виділяють більше кількох гігабайт пам'яті). Звичайно, такий JVM не варто згадувати і є лише іграшкою (як це реалізація mallocдля C). Див. Epsilon NoOp GC для отримання додаткової інформації. Реалістичні JVM - це дуже складні програми та поєднують декілька методів збору сміття.

Крім того, Java не є тим самим, що й JVM, і у вас є реалізація Java, що працює без JVM (наприклад, дострокові компілятори Java, час виконання Android ). У деяких випадках (здебільшого академічні) ви можете уявити собі (так звані методи "збирання сміття під час компіляції"), що програма Java не виділяє та не видаляє під час виконання (наприклад, тому що компілятор оптимізації був досить розумним, щоб використовувати лише стек виклику та автоматичні змінні ).

Чому об’єкти Java не видаляються відразу після того, як на них більше не посилаються?

Оскільки специфікації Java та JVM цього не потребують.


Прочитайте посібник з GC для отримання додаткової інформації (та специфікації JVM ). Зауважте, що живий (або корисний для майбутніх обчислень) об’єкт - це властивість цілої програми (немодульна).

Objective-C віддає перевагу підходу підрахунку посилань до управління пам'яттю . І це також має підводні камені (наприклад, Objective-C програміст повинен піклуватися про циклічних посиланнях на expliciting слабких посилань, але JVM обробляє циклічні посилання добре на практиці , не вимагаючи уваги з боку програміста Java).

Там немає срібної кулі в програмуванні та розробці мов програмування (бути в курсі Проблеми Зупинки , будучи корисним живим об'єктом є нерозв'язним в цілому).

Ви також можете прочитати SICP , Прагматику мови програмування , Книгу Драконів , Слухати невеликими п’єсами та Операційні системи: Три простих п’єси . Вони не про Java, але вони відкриють вам розум і повинні допомогти зрозуміти, що повинен робити JVM і як це може практично працювати (з іншими фрагментами) на вашому комп'ютері. Ви також можете витратити багато місяців (або кілька років) на вивчення складного вихідного коду існуючих реалізацій JVM з відкритим кодом (наприклад, OpenJDK , який має кілька мільйонів рядків вихідного коду).


20
"Можливо, наївний JVM, який ніколи не видаляє об'єкти і не звільняє пам'ять, може відповідати специфікаціям" Це, безумовно, відповідає специфікації! Java 11 фактично додає непридатний збирач сміття для, серед іншого, дуже короткочасних програм.
Майкл

6
"Ви не повинні турбуватися, коли об'єкт видаляється" Не погоджуюсь. Для одного, ви повинні знати, що RAII вже не є можливою схемою, і що ви не можете залежати від finalizeбудь-якого управління ресурсами (файлових файлів, db-з'єднань, gpu-ресурсів тощо).
Олександр

4
@Michael Це має ідеальний сенс для пакетної обробки з використовуваною стелею пам'яті. ОС може просто сказати, "вся пам'ять, яка використовується цією програмою, вже зникла!" зрештою, що досить швидко. Дійсно, багато програм на C написано саме так, особливо в ранньому світі Unix. Паскаль мав надзвичайно жахливий "скидання покажчика стека / купи на попередньо збережений контрольний пункт", що дозволило вам зробити те саме, хоча це було досить небезпечно - позначте, запустіть підзадачу, скиньте.
Луань

6
@Alexander взагалі поза C ++ (і декількома мовами, які навмисно випливають з нього), якщо припустити, що RAII працюватиме лише на основі фіналізаторів - це анти-шаблон, який слід попередити і замінити на явний блок управління ресурсами. Вся суть GC полягає в тому, що термін експлуатації та ресурсів, зрештою, відокремлюються.
Левшенко

3
@ Левшенко я категорично не погоджуюся з тим, що "час життя та ресурс нерозв'язані" - це "вся суть" ГК. Це негативна ціна, яку ви платите за основну точку GC: просте, безпечне управління пам’яттю. "якщо припустити, що RAII працюватиме лише на основі фіналізаторів, це анти-шаблон" У Java? Можливо, Але не в CPython, Rust, Swift або Objective C. "попереджаються проти та замінюються явним блоком управління ресурсами" Ні, вони суворіше обмежені. Об'єкт, що управляє ресурсом через RAII, дає вам ручку, щоб пройти масштабне життя навколо. Блок спробу використання ресурсів обмежений однією областю.
Олександр

23

Щоб використовувати термінологію Objective-C, всі посилання на Java по суті є "сильними".

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

Якщо в Objective-C об'єкт більше не має чітких посилань, об'єкт видаляється негайно.

Це також не обов'язково правильно - деякі версії Objective C дійсно використовували генераційний сміттєзбірник. В інших версіях взагалі не було збирання сміття.

Це правда, що в нових версіях Objective C використовується автоматичний підрахунок посилань (ARC), а не GC, заснований на трасах, і це (часто) призводить до того, що об'єкт "видаляється", коли число посилань дорівнює нулю. Однак зауважте, що реалізація JVM також може бути сумісною і працювати саме таким чином (чорт, вона може бути сумісною і взагалі не мати GC.)

То чому б більшість реалізацій JVM не роблять цього, а замість цього використовують алгоритми GC, засновані на трасах?

Простіше кажучи, ARC не такий утопічний, як спочатку здається:

  • Ви повинні збільшувати чи зменшувати лічильник щоразу, коли посилання копіюється, змінюється або виходить за межі сфери, що призводить до очевидних накладних витрат.
  • ARC не може легко очистити циклічні посилання, оскільки всі вони мають посилання один на одного, тому їх кількість відліку ніколи не дорівнює нулю.

Звичайно, ARC має свої переваги - його простота у виконанні та колекція є детермінованою. Але наведені вище недоліки, серед інших, є причиною того, що більшість реалізацій JVM використовуватимуть генераційний, заснований на трасах GC.


1
Найсмішніше, що Apple перейшла на ARC саме тому, що побачила, що на практиці вона значно перевершує інші ГК (зокрема, покоління). Справедливо кажучи, це в основному стосується платформ, обмежених пам'яттю (iPhone). Але я би протиставив ваше твердження, що "ARC не такий утопічний, як здається спочатку", кажучи, що покоління (та інші недетерміновані) GC не такі утопічні, як здається спочатку: детерміновані руйнування, мабуть, кращий варіант у переважна більшість сценаріїв.
Конрад Рудольф

3
@KonradRudolph, хоча я і досить прихильник детермінованого знищення, я не думаю, що "кращий варіант у переважній більшості сценаріїв" стоїть на місці. Це, безумовно, кращий варіант, коли затримка або пам'ять важливіша за середню пропускну здатність, і особливо, коли логіка досить проста. Але це не так, як не так вже й багато складних додатків, які вимагають багато циклічних посилань тощо і вимагають швидкої середньої роботи, але насправді не хвилюються затримки та мають достатньо пам’яті. Для них сумнівно, чи ARC є хорошою ідеєю.
близько

1
@leftaroundabout У «більшості сценаріїв» ні пропускна здатність, ні тиск пам’яті не є вузьким місцем, тому це не має значення ні в якому разі. Ваш приклад - це один конкретний сценарій. Зрозуміло, це не дуже рідко, але я б не пішов настільки далеко, щоб стверджувати, що це частіше, ніж інші сценарії, де ARC краще підходить. Крім того, ARC може добре працювати з циклами. Просто потрібне просте ручне втручання програміста. Це робить його менш ідеальним, але навряд чи він може бути розривом. Я стверджую, що детерміновані доопрацювання є набагато важливішою характеристикою, ніж ви робите вигляд.
Конрад Рудольф

3
@KonradRudolph Якщо ARC вимагає простого ручного втручання програміста, він не має справу з циклами. Якщо ви почнете активно використовувати списки з подвійним зв'язком, ARC переходить до ручного розподілу пам'яті. Якщо у вас є великі довільні графіки, ARC змушує вас писати сміттєзбірник. Аргумент GC полягав би в тому, що ресурси, які потребують знищення, не є завданнями підсистеми пам'яті, і для відстеження відносно небагато з них вони повинні бути детерміновано доопрацьовані шляхом простого ручного втручання програміста.
профілі

2
@KonradRudolph ARC і цикли принципово призводять до витоку пам'яті, якщо вони не обробляються вручну. У досить складних системах можуть відбуватися великі витоки, якщо наприклад, якийсь об'єкт, що зберігається на карті, зберігає посилання на цю карту, зміни, які може бути зроблено програмістом, який не відповідає за розділи коду, що створюють та знищують цю карту. Великі довільні графіки не означають, що внутрішні покажчики не сильні, що нормально зникати елементи, пов'язані з цим. Якщо проблема з витоком пам’яті є меншою проблемою, ніж необхідність закривати файли вручну, я не скажу, але це реально.
профілі

5

Java не вказує точно, коли об’єкт збирається, оскільки це дає реалізаціям свободу вибору способу поводження зі сміттям.

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

У багатопотоковому коді вартість вище. Він або вимагає атомних приростів / зменшень, або блокування, обидва вони можуть бути дорогими. На сучасному процесорі атомна операція може бути на 20 разів дорожчою, ніж проста операція реєстрації (очевидно, що варіюється від процесора до процесора). Це може збільшити вартість.

Тож з цим ми можемо розглянути компроміси, зроблені кількома моделями.

  • Objective-C зосереджується на ARC - автоматизованому підрахунку довідок. Їх підхід полягає у використанні підрахунку довідок для всього. Не існує виявлення циклу (що я знаю), тому очікується, що програмісти запобігають виникненню циклів, що коштує часу на розробку. Їх теорія полягає в тому, що покажчикам не призначається все так часто, і їх компілятор може ідентифікувати ситуації, коли збільшення / зменшення опорних підрахунків не може спричинити загибель об'єкта, і вилучити ці прирости / декременти повністю. Таким чином, вони мінімізують витрати на підрахунок еталонів.

  • CPython використовує гібридний механізм. Вони використовують довідкові підрахунки, але також мають сміттєзбірник, який ідентифікує цикли та випускає їх. Це забезпечує переваги обох світів ціною обох підходів. CPython повинен підтримувати кількість відліку тазробіть ведення книги для виявлення циклів. CPython позбавляється від цього двома способами. Кулак полягає в тому, що CPython насправді не є повністю багатопоточним. Він має замок, відомий як GIL, який обмежує багатопотоковість. Це означає, що CPython може використовувати звичайні прирости / декременти, а не атомні, що набагато швидше. CPython також інтерпретується, що означає, що такі операції, як присвоєння змінній, вже займають кілька інструкцій, а не просто 1. Зайві витрати на збільшення приросту / зменшення, що швидко робиться в коді С, є меншою проблемою, тому що ми ' Ви вже заплатили цю вартість.

  • Java знижує підхід взагалі не гарантуючи систему відліку. Дійсно, специфікація не говорить нічого про те , як об'єкти управляються крім того , що буде автоматична система управління зберіганням даних. Однак специфікація також сильно натякає на припущення, що це сміття, зібране таким чином, що обробляє цикли. Не уточнюючи, коли термін дії закінчується, java отримує свободу користування колекторами, які не витрачають час на збільшення / зменшення часу. Дійсно, розумні алгоритми, такі як покоління збирачів сміття, можуть навіть обробляти безліч простих випадків, навіть не дивлячись на дані, що відновлюються (вони повинні лише переглянути дані, на які все ще посилаються).

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


4

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

Мета збирання сміття - не знищення об'єктів, на які немає посилань, а в тому, щоб досягти трьох речей:

  1. Недійсні слабкі посилання, що ідентифікують об'єкти, які не мають жодного сильнодоступного посилання, пов'язаного з ними.

  2. Шукайте у списку об'єктів системи за допомогою фіналізаторів, щоб побачити, чи немає у них жодного сильнодоступного посилання, пов’язаного з ними.

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

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


5
Власне, gc має лише одну мету: моделювання нескінченної пам'яті. Все, що ви назвали ціллю, є або недосконалістю абстракції, або деталізацією реалізації.
Дедуплікатор

@Deduplicator: Слабкі посилання пропонують корисну семантику, яку неможливо досягти без допомоги GC.
supercat

Звичайно, слабкі посилання мають корисну семантику. Але чи потрібна була б ця семантика, якби симуляція була кращою?
Дедуплікатор

@ Дедуплікатор: Так. Розглянемо колекцію, яка визначає, як оновлення взаємодіють із перерахуванням. Така колекція може потребувати слабких посилань на будь-які живі перелічувачі. У системі з необмеженою пам’яттю колекція, яка повторювалася повторно, мала б список зацікавлених перелічувачів безперервно зростати. Пам'ять, необхідна для цього списку, не буде проблемою, але час, необхідний для його повторення, погіршить продуктивність системи. Додавання GC може означати різницю між алгоритмом O (N) та O (N ^ 2).
supercat

2
Чому ви хочете повідомити про це перелічувачів, замість того, щоб додавати їх до списку та дозволяти їм шукати себе під час їх використання? І будь-яка програма, залежно від сміття, що обробляється своєчасно, а не залежно від тиску пам’яті, все одно живе в гріху, якщо вона взагалі рухається.
Дедуплікатор

4

Дозвольте запропонувати переформулювати та узагальнити ваше запитання:

Чому Java не дає надійних гарантій щодо свого процесу GC?

Зважаючи на це, швидко прокрутіть відповіді тут. Наразі сім (не рахуючи цього), із досить нитки коментарів.

Це ваша відповідь.

ГК важко. Є багато міркувань, багато різних компромісів, і, зрештою, дуже багато різних підходів. Деякі з цих підходів дозволяють зробити GC об'єктом, як тільки він не потрібен; інші - ні. Зберігаючи контракт вільним, Java надає своїм виконавцям більше можливостей.

Навіть у цьому рішенні є компроміс: звичайно, тримаючи контракт вільним, Java в основному * забирає можливість програмістів покладатися на деструкторів. Це те, що зокрема програмісти на C ++ часто пропускають ([потрібне цитування];)), тому це не є незначним компромісом. Я не бачив обговорення цього конкретного мета-рішення, але, мабуть, люди з Java вирішили, що переваги від наявності більшої кількості опцій GC перевищують переваги того, що зможуть точно сказати програмістам, коли об’єкт буде знищений.


* Існує finalizeметод, але з різних причин, які не відповідають обставинам для цієї відповіді, на це важко і недобре покластися.


3

Існує дві різні стратегії поводження з пам'яттю без явного коду, написаного розробником: Збір сміття та підрахунок посилань.

Збір сміття має ту перевагу, що він "працює", якщо розробник не зробить щось дурне. За допомогою підрахунку посилань ви можете мати еталонні цикли, це означає, що це "працює", але розробнику іноді доводиться бути розумним. Тож це плюс до вивезення сміття.

При підрахунку посилань об'єкт відмирає негайно, коли число відліку опускається до нуля. Це перевага для підрахунку довідок.

Швидко, збирання сміття відбувається швидше, якщо вірити любителям збору сміття, а підрахунок посилань - швидше, якщо ви вірите любителям підрахунку довідок.

Це лише два різні методи для досягнення однієї і тієї ж мети. Java вибрала один метод, Objective-C вибрав інший (і додав багато підтримки компілятора, щоб змінити його від болю в дупі на щось, що для розробників мало.)

Переміна Java з вивезення сміття на підрахунок посилань було б головним завданням, оскільки знадобиться багато змін коду.

Теоретично, Java могла реалізувати суміш збору сміття та підрахунку довідок: Якщо довідкова кількість дорівнює 0, то об'єкт недоступний, але не обов'язково навпаки. Таким чином, ви можете зберігати підрахунки та видаляти об'єкти, коли їх посилання дорівнює нулю (а потім періодично запускати збирання сміття, щоб ловити об’єкти протягом недосяжних еталонних циклів). Я думаю, що світ розділений на 50/50 серед людей, які вважають, що додавання підрахунку посилань до збирання сміття є поганою ідеєю, і люди, які вважають, що додавання сміття до підрахунку посилань - погана ідея. Тож цього не відбудеться.

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


Що стосується підрахунку довідок, то доопрацювання є тривіальним, оскільки програміст подбав про цикли. Що стосується gc, цикли тривіальні, але програміст повинен бути обережним при доопрацюванні.
Дедуплікатор

@Deduplicator В Java, це також можна створювати сильні посилання на об'єкти допрацьовується ... В Objective-C і Swift, коли лічильник посилань дорівнює нулю, то об'єкт буде зникати (якщо ви поклали нескінченний цикл в dealloc / деист).
gnasher729

Щойно помітив дурну перевірку орфографії, яка замінює deinit на deist ...
gnasher729

1
Є причина, чому більшість програмістів ненавидять автоматичну корекцію правопису ... ;-)
Deduplicator

хаха ... Я думаю, що світ розділений на 0,1 / 0,1 / 99,8 між людьми, які вважають, що додавання підрахунку посилань до збирання сміття є поганою ідеєю, і людьми, які вважають, що додавання сміття до підрахунку посилань - погана ідея, і людьми, які продовжуйте рахувати дні, поки не прийде сміття, тому що ця тона знову стає смердючою ...
ліворуч

1

Всі інші аргументи щодо ефективності та дискусії щодо складності розуміння, коли більше немає посилань на об'єкт, є правильними, хоча одна інша ідея, яку, на мою думку, варто згадати, - це щонайменше один JVM (azul), який вважає щось подібне в тому, що він реалізує паралельну gc, яка по суті має потік vm, який постійно перевіряє посилання для спроби видалити їх, які будуть діяти не зовсім аналогічно тому, що ви говорите. В основному, він буде постійно оглядатись навколо купи і намагатися відновити будь-яку пам'ять, на яку не йдеться. Це спричиняє дуже невелику вартість продуктивності, але це призводить до фактично нульового або дуже короткого часу GC. (Тобто, якщо постійно збільшується розмір купи не перевищує системну оперативну пам'ять, і тоді Azul плутається, а потім з’являються дракони)

TLDR Щось подібне існує для JVM, це просто спеціальний jvm і має недоліки, як і будь-який інший інженерний компроміс.

Відмова: У мене немає зв’язків з Azul, ми просто використовували його на попередній роботі.


1

Максимізація стійкої пропускної здатності або мінімізація затримки gc знаходяться в динамічному напруженні, що, мабуть, є найпоширенішою причиною, чому GC виникає не відразу. У деяких системах, як-от 911 програм для надзвичайних ситуацій, не досягаючи певного порогу затримки, можна почати запускати процеси відмови сайту. В інших, як, наприклад, у банківському та / або арбітражному майданчиках, набагато важливіше максимізувати пропускну здатність.


0

Швидкість

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


Ну, я впевнений, що ми знайдемо більш цікаві способи використання додаткових циклів, ніж це.
Дедуплікатор
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.