В Java, как только у объекта больше нет ссылок, он становится пригодным для удаления, но JVM решает, когда объект фактически удален. Чтобы использовать терминологию Objective C, все ссылки на Java по своей природе являются "сильными". Однако в Objective-C, если у объекта больше нет сильных ссылок, объект немедленно удаляется. Почему это не так в Java?
java
garbage-collection
moonman239
источник
источник
Ответы:
Прежде всего, у Java есть слабые ссылки и еще одна категория лучших усилий, называемая мягкими ссылками. Слабые и сильные ссылки - это совершенно отдельная проблема от подсчета ссылок и сбора мусора.
Во-вторых, существуют схемы использования памяти, которые могут сделать сборку мусора более эффективной во времени, жертвуя пространством. Например, более новые объекты гораздо чаще удаляются, чем более старые объекты. Поэтому, если вы подождете немного между развертками, вы можете удалить большую часть памяти нового поколения, перенеся несколько выживших в долговременное хранилище. Это более длительное хранение можно сканировать гораздо реже. Немедленное удаление с помощью ручного управления памятью или подсчета ссылок намного более подвержено фрагментации.
Это похоже на разницу между походом в магазин за едой один раз за зарплату и ежедневным посещением магазина, чтобы получить достаточно еды на один день. Ваша одна большая поездка займет намного больше времени, чем отдельная маленькая поездка, но в целом вы сэкономите время и, возможно, деньги.
источник
Потому что правильно знать что-то, на которое больше не ссылаются, нелегко. Даже близко не легко.
Что если у вас есть два объекта, ссылающихся друг на друга? Они остаются навсегда? Расширив эту линию мышления до разрешения любой произвольной структуры данных, вы скоро поймете, почему JVM или другие сборщики мусора вынуждены применять гораздо более сложные методы определения того, что еще нужно и что можно предпринять.
источник
AFAIK, спецификация JVM (написана на английском языке) не упоминает, когда именно объект (или значение) должен быть удален, и оставляет это для реализации (аналогично для R5RS ). Это как-то требует или предлагает сборщик мусора, но оставляет детали для реализации. И аналогично для спецификации Java.
Помните, что языки программирования - это спецификации ( синтаксиса , семантики и т. Д.), А не программные реализации. Язык как Java (или его JVM) имеет много реализаций. Его спецификация опубликована , доступна для скачивания (чтобы вы могли ее изучить) и написана на английском языке. §2.5.3 В куче спецификации JVM упоминается сборщик мусора:
(акцент мой; финализация BTW упоминается в §12.6 спецификации Java, а модель памяти - в §17.4 спецификации Java)
Поэтому (в Java) вас не должно волновать, когда объект удаляется , и вы можете кодировать как-будто это не происходит (рассуждая в абстракции, где вы игнорируете это). Конечно, вам нужно заботиться о потреблении памяти и множестве живых объектов, это другой вопрос. В нескольких простых случаях (например, программа «Здравствуй, мир») вы можете доказать - или убедить себя - что выделенная память довольно мала (например, меньше гигабайта), и тогда вам все равно удаление отдельных объектов. В большем количестве случаев вы можете убедить себя, что живые объекты(или достижимые, которые являются надмножеством - проще для рассуждений о живых) никогда не превышают разумный предел (и тогда вы полагаетесь на GC, но вам все равно, как и когда происходит сборка мусора). Читайте о космической сложности .
Я предполагаю, что в нескольких реализациях JVM, работающих с недолговечной Java-программой, такой как hello world, сборщик мусора вообще не запускается и удаление не происходит. AFAIU, такое поведение соответствует многочисленным спецификациям Java.
В большинстве реализаций JVM используются методы копирования поколений (по крайней мере, для большинства объектов Java, тех, которые не используют финализацию или слабые ссылки ; завершение не гарантируется в течение короткого времени и может быть отложено, так что это всего лишь полезная функция, которую ваш код не должен во многом зависит от), в котором понятие удаления отдельного объекта не имеет никакого смысла (поскольку большой блок памяти, содержащий зоны памяти для многих объектов, - возможно, несколько мегабайт одновременно, освобождается одновременно).
Если бы спецификация JVM требовала, чтобы каждый объект был удален точно как можно скорее (или просто наложил бы больше ограничений на удаление объекта), эффективные методы генерации GC были бы запрещены, и разработчики Java и JVM были бы мудры избегать этого.
Кстати, вполне возможно, что наивная JVM, которая никогда не удаляет объекты и не освобождает память, может соответствовать спецификациям (букве, а не духу) и, безусловно, способна на практике использовать мир приветствия (обратите внимание, что большинство крошечные и недолговечные Java-программы, вероятно, не выделяют более нескольких гигабайт памяти). Конечно, о такой JVM не стоит упоминать, и это просто игрушка (как в этой реализации
malloc
для C). См. Epsilon NoOp GC для получения дополнительной информации. Реальные виртуальные машины Java являются очень сложными программными компонентами и сочетают в себе несколько методов сбора мусора.Кроме того, Java отличается от JVM, и у вас есть реализации Java, работающие без JVM (например, опережающие компиляторы Java, среда выполнения Android ). В некоторых случаях (в основном академических) вы можете себе представить (так называемые методы «сборки мусора во время компиляции»), что Java-программа не выделяет и не удаляет во время выполнения (например, потому что оптимизирующий компилятор достаточно умен, чтобы использовать только стек вызовов и автоматические переменные ).
Потому что спецификации Java и JVM этого не требуют.
Прочтите руководство GC для получения дополнительной информации (и спецификации JVM ). Обратите внимание, что наличие объекта (или полезного для будущих вычислений) для объекта является свойством всей программы (немодульным).
Objective-C предпочитает подход подсчета ссылок к управлению памятью . И это также имеет подводные камни (например, Objective-C программист должен заботиться о циклических ссылках на expliciting слабых ссылок, но JVM обрабатывает циклические ссылки хорошо на практике , не требуя внимания со стороны программиста Java).
В программировании и разработке языков программирования не существует «Серебряной пули» (помните о проблеме остановки ; быть полезным живым объектом в целом неразрешимо ).
Вы также можете прочитать SICP , Прагматика языка программирования , Книга Дракона , Lisp In Small Pieces и Операционные системы: Три простых компонента . Они не о Java, но они откроют вам разум и должны помочь понять, что должна делать JVM и как она может практически работать (с другими компонентами) на вашем компьютере. Вы также можете потратить много месяцев (или несколько лет) на изучение сложного исходного кода существующих реализаций JVM с открытым исходным кодом (например, OpenJDK , который имеет несколько миллионов строк исходного кода).
источник
finalize
какого управления ресурсами (файловых дескрипторов, соединений db, ресурсов gpu и т. Д.).Это не правильно - у Java есть и слабые, и мягкие ссылки, хотя они реализованы на уровне объектов, а не в качестве ключевых слов языка.
Это также не обязательно правильно - некоторые версии Objective C действительно использовали сборщик мусора поколений. В других версиях сборка мусора вообще отсутствовала.
Это правда, что в более новых версиях Objective C используется автоматический подсчет ссылок (ARC), а не GC на основе трассировки, и это (часто) приводит к тому, что объект «удаляется», когда этот счетчик ссылок достигает нуля. Однако обратите внимание, что реализация JVM также может быть совместимой и работать именно таким образом (черт возьми, она может быть совместимой и вообще не иметь GC).
Так почему же большинство реализаций JVM не делают этого, а вместо этого используют алгоритмы GC на основе трассировки?
Проще говоря, ARC не так утопичен, как кажется на первый взгляд:
Конечно, у ARC есть свои преимущества - его легко реализовать, а его сбор является детерминированным. Но вышеперечисленные недостатки, среди прочего, являются причиной того, что большинство реализаций JVM будут использовать GC, основанный на трассировке поколений.
источник
Java не указывает точно, когда объект собирается, потому что это дает реализациям свободу выбора, как обрабатывать сборку мусора.
Существует много различных механизмов сбора мусора, но те, которые гарантируют, что вы можете собрать объект немедленно, почти полностью основаны на подсчете ссылок (я не знаю ни одного алгоритма, который нарушает эту тенденцию). Подсчет ссылок является мощным инструментом, но он требует затрат на поддержание количества ссылок. В однопотоковом коде это не что иное, как приращение и уменьшение, поэтому назначение указателя может стоить в 3 раза дороже в коде с подсчетом ссылок, чем в коде без подсчета ссылок (если компилятор может выполнить все до машины) код).
В многопоточном коде стоимость выше. Он либо требует атомарных приращений / уменьшений, либо блокировок, которые могут быть дорогими. На современном процессоре атомарная операция может быть в 20 раз дороже, чем простая операция с регистром (очевидно, варьируется от процессора к процессору). Это может увеличить стоимость.
Таким образом, с этим мы можем рассмотреть компромиссы, сделанные несколькими моделями.
Objective-C ориентирован на ARC - автоматический подсчет ссылок. Их подход заключается в использовании подсчета ссылок для всего. Обнаружения циклов (насколько мне известно) не существует, поэтому программисты должны предотвращать возникновение циклов, что стоит времени на разработку. Их теория состоит в том, что указатели назначаются не так часто, и их компилятор может идентифицировать ситуации, в которых увеличение / уменьшение количества ссылок не может привести к смерти объекта, и полностью исключить эти приращения / уменьшения. Таким образом, они сводят к минимуму стоимость подсчета ссылок.
CPython использует гибридный механизм. Они используют счетчики ссылок, но у них также есть сборщик мусора, который идентифицирует циклы и освобождает их. Это обеспечивает преимущества обоих миров за счет обоих подходов. CPython должен поддерживать количество ссылок ивести бухгалтерский учет, чтобы обнаружить циклы. CPython сходит с рук двумя способами. Во-первых, CPython не является полностью многопоточным. Он имеет блокировку, известную как GIL, которая ограничивает многопоточность. Это означает, что CPython может использовать обычные инкременты / декременты, а не атомарные, что намного быстрее. CPython также интерпретируется, что означает, что такие операции, как присваивание переменной, уже занимают несколько инструкций, а не просто 1. Дополнительные затраты на выполнение приращений / уменьшений, которые выполняются быстро в коде C, меньше проблем, потому что мы ' Мы уже оплатили эту стоимость.
Java использует подход, который вообще не гарантирует систему подсчета ссылок. Действительно, спецификация не говорит ничего о том , как объекты управляются кроме того , что будет автоматическая система управления хранением данных. Тем не менее, спецификация также сильно намекает на предположение, что это будет сборка мусора способом, который обрабатывает циклы. Не указывая, когда объекты истекают, Java получает свободу использовать коллекторы, которые не тратят время на увеличение / уменьшение. Действительно, умные алгоритмы, такие как сборщики мусора поколений, могут даже обрабатывать многие простые случаи, даже не глядя на данные, которые возвращаются (им нужно только смотреть на данные, на которые все еще ссылаются).
Таким образом, мы можем видеть, что каждый из этих трех должен был пойти на компромисс. Какой компромисс является наилучшим, во многом зависит от того, как язык предназначен для использования.
источник
Хотя
finalize
сборка мусора была поддержана Java GC, сборка мусора по своей сути интересует не мертвые объекты, а живые. В некоторых системах GC (возможно, включая некоторые реализации Java) единственное, что отличает группу битов, представляющих объект, от группы хранилищ, которая ни для чего не используется, - это наличие ссылок на первую. Хотя объекты с финализаторами добавляются в специальный список, другие объекты могут не иметь ничего в юниверсе, в котором говорится, что их хранилище связано с объектом, за исключением ссылок, хранящихся в пользовательском коде. Когда последняя такая ссылка будет перезаписана, битовая комбинация в памяти немедленно перестанет распознаваться как объект, независимо от того, осознает ли это что-либо во вселенной или нет.Целью сборки мусора является не уничтожение объектов, на которые нет ссылок, а скорее выполнение трех задач:
Признать недействительными слабые ссылки, которые идентифицируют объекты, которые не имеют сильно достижимых ссылок, связанных с ними.
Выполните поиск в системном списке объектов с помощью финализаторов, чтобы выяснить, не связаны ли какие-либо из них с сильно достижимыми ссылками.
Определите и консолидируйте области хранения, которые не используются никакими объектами.
Обратите внимание, что основной целью GC является №3, и чем дольше он ждет, тем больше возможностей будет при консолидации. Имеет смысл сделать № 3 в тех случаях, когда кто-то может немедленно использовать хранилище, но в противном случае имеет смысл отложить его.
источник
Позвольте мне предложить переписку и обобщение вашего вопроса:
Имея это в виду, сделайте быстрый просмотр ответов здесь. На данный момент их семь (не считая этого), с несколькими комментариями.
Это твой ответ.
GC это сложно. Есть много соображений, много разных компромиссов, и, в конечном итоге, много разных подходов. Некоторые из этих подходов делают возможным GC-объект, как только он не нужен; другие нет. Оставляя контракт свободным, Java предоставляет его реализаторам больше возможностей.
Конечно, даже в этом решении есть компромисс: поддерживая контракт, Java в основном * лишает программистов возможности полагаться на деструкторы. Это то, что программисты C ++, в частности, часто упускают ([цитата нужна];)), так что это не незначительный компромисс. Я не видел обсуждения этого конкретного мета-решения, но, вероятно, люди Java решили, что преимущества наличия большего количества опций GC перевешивают преимущества возможности точно указать программистам, когда объект будет уничтожен.
* Существует
finalize
метод, но по разным причинам, которые выходят за рамки этого ответа, на него трудно и не стоит полагаться.источник
Существует две разные стратегии обработки памяти без явного кода, написанного разработчиком: сборка мусора и подсчет ссылок.
Преимущество сборки мусора в том, что она «работает», если разработчик не делает глупостей. С подсчетом ссылок вы можете иметь циклы ссылок, что означает, что он «работает», но разработчик иногда должен быть умным. Так что это плюс для сбора мусора.
При подсчете ссылок объект сразу исчезает, когда счетчик ссылок падает до нуля. Это преимущество для подсчета ссылок.
По быстрому сборка мусора происходит быстрее, если вы верите поклонникам сбора мусора, а подсчет ссылок - быстрее, если вы верите поклонникам подсчета ссылок.
Это всего лишь два разных метода для достижения одной и той же цели, Java выбрал один метод, Objective-C выбрал другой (и добавил много поддержки компилятора, чтобы изменить его с боли в заднице на что-то, что не требует больших усилий для разработчиков).
Переключение Java с сборки мусора на подсчет ссылок было бы серьезной задачей, потому что потребовалось бы много изменений кода.
Теоретически, в Java могла бы быть реализована смесь сбора мусора и подсчета ссылок: если счетчик ссылок равен 0, то объект недоступен, но не обязательно наоборот. Таким образом, вы могли бы вести подсчет ссылок и удалять объекты, когда их счетчик ссылок равен нулю (а затем запускать сборку мусора время от времени, чтобы ловить объекты в недостижимых ссылочных циклах). Я думаю, что мир разделен на 50/50 людей, которые считают, что добавление подсчета ссылок в сборщик мусора является плохой идеей, и люди, которые думают, что добавление сбора мусора в подсчет ссылок является плохой идеей. Так что этого не произойдет.
Таким образом, Java может немедленно удалять объекты, если их счетчик ссылок становится равным нулю, и позже удалять объекты в недостижимых циклах. Но это дизайнерское решение, а Java отказалась от него.
источник
Все остальные аргументы производительности и дискуссии о сложности понимания, когда больше нет ссылок на объект, верны, хотя есть еще одна идея, о которой стоит упомянуть, - это то, что есть хотя бы одна JVM (azul), которая рассматривает что-то подобное тем, что он реализует параллельный gc, который по существу имеет поток vm, постоянно проверяющий ссылки, чтобы попытаться удалить их, что будет действовать не совсем так, как вы говорите. В основном он будет постоянно оглядываться в куче и пытаться восстановить любую память, на которую нет ссылок. Это приводит к очень небольшим затратам производительности, но приводит к практически нулевому или очень короткому времени GC. (То есть, если постоянно увеличивающийся размер кучи превышает системную оперативную память, а затем Azul запутывается, а затем появляются драконы)
TLDR Нечто подобное существует для JVM, это просто специальный jvm, и у него есть недостатки, как у любого другого инженерного компромисса.
Отказ от ответственности: у меня нет связей с Азулом, мы просто использовали его на предыдущей работе.
источник
Максимизация постоянной пропускной способности или минимизация задержки gc находятся в динамическом напряжении, что, вероятно, является наиболее распространенной причиной, по которой GC не происходит немедленно. В некоторых системах, например в 911 приложениях для экстренных случаев, несоблюдение определенного порогового значения задержки может привести к запуску процессов аварийного переключения сайта. В других, таких как банковские и / или арбитражные сайты, гораздо важнее максимизировать пропускную способность.
источник
скорость
Почему все это происходит в конечном итоге из-за скорости. Если процессоры были бесконечно быстрыми или (если быть практичным) близкими к нему, например, 1 000 000 000 000 000 000 000 000 000 000 000 операций в секунду, то между каждым оператором могут происходить невероятно длинные и сложные вещи, например, удаление удаленных объектов. Поскольку такое количество операций в секунду в настоящее время не соответствует действительности, и, как объясняют большинство других ответов, на самом деле это сложно и требует значительных ресурсов, сборщик мусора существует, так что программы могут сосредоточиться на том, чего они на самом деле пытаются достичь в быстрая манера.
источник