Какова первоначальная стоимость объекта, подлежащего доработке?

При обсуждении финализируемых объектов в Java обычно обсуждаются общие косвенные издержки, возникающие, когда финализируемые объекты (и связанные с ними ресурсы) не могут быть быстро удалены сборщиком мусора.

На данный момент меня больше интересует, какова фактическая прямая стоимость финализации, как с точки зрения памяти, так и с точки зрения времени выделения объекта. Я видел косвенные ссылки на существование такой стоимости в ряде мест, например, В статье Oracle о проблемах сохранения памяти при финализации отмечается:

Когда выделяется obj, JVM внутренне записывает, что obj является финализируемым. Обычно это замедляет быстрый путь распределения, который есть у современных JVM.

Как JVM регистрирует, что экземпляр объекта может быть финализирован, и каковы затраты памяти и производительности на это?

Для тех, кто интересуется моим конкретным приложением:

Мы производим и храним миллионы невероятно легких предметов; добавление одного указателя к этим объектам невероятно затратно, поэтому мы проделали немало работы, чтобы удалить из них указатели, вместо этого используя меньшие числовые идентификаторы, упакованные в подмножество битов поля. Распаковка номера позволяет получить общее неизменяемое свойство с этим идентификатором из пула, в котором они хранятся с использованием карты.

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

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

Одним из вариантов обеспечения этого уменьшения является добавление следующего метода финализации:

public void finalize() {
    Pool.release(getPropertyId());
}

Однако, если само действие финализации означает необходимость сохранения дополнительного указателя на объект, первоначальные затраты на финализацию будут считаться высокими для этого приложения. Если это означает, что должны быть выделены дополнительные объекты, это почти наверняка будет слишком дорого... отсюда мой вопрос: каковы прямые первоначальные затраты на финализацию?


person Theodore Murdock    schedule 08.05.2015    source источник
comment
Не является ли плохой практикой полагаться на финализацию объектов? я имею в виду, никогда не гарантируется, что finalize() будет вызван - или?   -  person vikingsteve    schedule 08.05.2015
comment
Возможно, вы захотите управлять своим собственным пулом объектов, а не полагаться на JVM. В зависимости от того, сколько объектов вам нужно одновременно, вы можете получить огромный прирост производительности, полностью избегая накладных расходов на выделение/сборку мусора/финализацию.   -  person Ted Hopp    schedule 08.05.2015
comment
Вы никогда не должны создавать так много финализируемых объектов, что это должно вызывать беспокойство. Вы должны использовать его только в крайнем случае, иначе вы увидите серьезную проблему с производительностью / стабильностью при очистке объектов. Вы можете не беспокоиться об этом, но используйте это много, и вы будете.   -  person Peter Lawrey    schedule 08.05.2015
comment
Можно ли использовать эталонную очередь вместо финализации? См., например. stackoverflow.com/questions/14450538/using-javas-referencequeue, как это может сработать.   -  person Louis Wasserman    schedule 08.05.2015
comment
@LouisWasserman Я мог бы, но я совершенно уверен, что выделение другого объекта для отслеживания того, был ли удален один из моих легковесных объектов, будет таким же или более дорогим, чем использование finalizable с точки зрения памяти. Делать это с фантомными ссылками было бы лучше с точки зрения того, чтобы не блокировать сборку мусора, если финализация слишком медленная, и моя собственная очередь, работающая с более высоким приоритетом, может работать быстрее, но выделение другого объекта, который требует аналогичного ведения JVM, вероятно, имеет даже более высокая первоначальная стоимость, чем возможность окончательной доработки.   -  person Theodore Murdock    schedule 08.05.2015
comment
@TheodoreMurdock: Обычно у вас есть одна очередь ссылок для всего приложения. Эталонные очереди обычно считаются значительно более безопасными и эффективными, чем финализация.   -  person Louis Wasserman    schedule 08.05.2015
comment
@PeterLawrey Я думаю, что это может быть так. Однако эти объекты, как правило, имеют очень длительный срок службы (измеряется в часах). Шаблоны использования могут различаться, пользователь может запросить перестроение всей базы данных, содержащей около 20 миллионов объектов, но чаще всего одновременно теряются и перестраиваются только несколько тысяч. Поскольку несколько ссылок на небольшой процент удаленных объектов могут быть сохранены, мы хотели бы, чтобы их свойства также оставались доступными для восстановления.   -  person Theodore Murdock    schedule 08.05.2015
comment
@LouisWasserman Конечно, есть только одна очередь, но есть n слабые или временные ссылочные объекты, если есть n объектов для очистки, и JVM должна делать заметки о каждой такой слабой ссылке, чтобы их можно было поставить в очередь, когда соответствующий объект является мусором собрал.   -  person Theodore Murdock    schedule 08.05.2015
comment
Обычно вы делаете так, чтобы один из ваших классов расширял WeakReference, чтобы у вас не было лишних объектов. Но даже в этом случае эта дополнительная информация обычно считается значительно более дешевой, чем доработка.   -  person Louis Wasserman    schedule 08.05.2015
comment
@LouisWasserman ... потому что люди обеспокоены возможностью сохранения памяти из-за того, что финализация не происходит быстро. Я только что провел быстрый тест, оказалось, что в моем случае все 20 миллионов объектов финализируются и мусор собирается очень быстро, без суеты, когда база данных должна быть перестроена. Так что единственная реальная проблема, которая меня беспокоит, — это первоначальная стоимость.   -  person Theodore Murdock    schedule 08.05.2015
comment
@TheodoreMurdock Как раз наоборот. Финализаторы дороже, чем PhantomReferences. Они также поддерживаются экземпляром java.lang.ref.Reference (FinalReference), поэтому они не могут быть дешевле, но их поведение сложнее. Более того, HotSpot JVM не использует встроенное выделение объектов, имеющих нетривиальный финализатор.   -  person apangin    schedule 08.05.2015
comment
@apangin Если вы знаете ответ, не могли бы вы ответить на мой вопрос?   -  person Theodore Murdock    schedule 08.05.2015
comment
@apangin Кроме того, действительно ли реализация PhantomReferences легче по весу? PhantomReference должен быть помещен в определенную очередь, поэтому JVM должна знать триплет информации: (фантомная ссылка, очередь для ее размещения, объект, который сигнализирует о том, что фантомная ссылка должна быть помещена в очередь). Действительно ли этот триплет информации сохраняется при меньших затратах памяти, чем простое знание того, что метод finalize объекта должен быть запущен, прежде чем он может быть удален сборщиком мусора? Если да, то как?   -  person Theodore Murdock    schedule 08.05.2015
comment
На самом деле, я должен изменить свой ответ @LouisWasserman ... после нескольких перестроений базы данных память действительно увеличилась из-за отсутствия финализации; похоже, я переоценил объем памяти, связанный с этими маленькими объектами (а не с их содержимым), поэтому мое первоначальное тестирование недооценило эффекты. В любом случае, поскольку ответ заключается в том, что для отслеживания простого финализируемого объекта должен быть выделен гораздо больший объект, первоначальная стоимость памяти слишком высока для моего приложения, независимо от производительности финализации, как я и подозревал. .   -  person Theodore Murdock    schedule 27.05.2015


Ответы (1)


Финализаторы ужасны не только из-за проблем с сохранением, но и с точки зрения производительности.

В Oracle JDK/OpenJDK объекты с методом finalize поддерживаются экземплярами Finalizer, подкласс java.lang.ref.Reference.

Все финализаторы регистрируются в конце конструктора объекта в два этапа: вызов с Java на виртуальную машину с последующим вызовом Finalizer.register(). Этот двойной переход Java->VM->Java не может быть встроен JIT-компилятором. Но хуже всего то, что конструктор Finalizer создает связанный список в разделе глобальная блокировка! (фейспалм)

Финализаторы также плохи с точки зрения занимаемой памяти: помимо всех полей Reference у них есть два дополнительных поля: next и prev.

PhantomReferences намного лучше финализаторов:

  • их построение не требует перехода на ВМ и обратно и может быть встроено;
  • у них нет дополнительных полей, кроме унаследованных от java.lang.ref.Reference;
  • глобальная синхронизация не производится.

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

Benchmark               Mode  Cnt       Score      Error   Units
Finalizer.finalizable  thrpt    5    2171,312 ± 1469,705  ops/ms
Finalizer.phantom      thrpt    5   61280,612 ±  692,922  ops/ms
Finalizer.plain        thrpt    5  225752,307 ± 7618,304  ops/ms
person apangin    schedule 08.05.2015
comment
Есть ли причина не исправлять глобальную блокировку в openJDK? - person qwwdfsad; 09.05.2015
comment
@qwwdfsad Я бы сказал, что нет причин для исправления. Зачем заботиться об унаследованном коде, который все равно не рекомендуется использовать. - person apangin; 10.05.2015