Как SoftReferences собираются JVM на практике? - PullRequest
17 голосов
/ 29 марта 2012

У меня в JVM работает два отдельных кэша (один контролируется сторонней библиотекой), каждый из которых использует программные ссылки. Я бы предпочел, чтобы JVM очистил мой контролируемый кеш, прежде чем тот, который контролируется библиотекой. Javadoc SoftReference заявляет:

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

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

Как обычные реализации JVM, особенно HotSpot, обрабатывают SoftReferences на практике? Являются ли они «предвзятым отношением к очистке недавно созданных или недавно использованных мягких ссылок», как это поощряется спецификацией?

Ответы [ 5 ]

7 голосов
/ 29 марта 2012

Похоже, что это может быть настроено, но это не так. Параллельный сборщик разметки зависает при реализации кучи по умолчанию must_clear_all_soft_refs(), которая, по-видимому, составляет всего true при выполнении _last_ditch_collection.

bool GenCollectedHeap::must_clear_all_soft_refs() {
  return _gc_cause == GCCause::_last_ditch_collection;
}

В то время как обычная обработка неудачного выделения имеет три последовательных вызова метода do_collect кучи, в CollectorPolicy.cpp

HeapWord* GenCollectorPolicy::satisfy_failed_allocation(size_t size,
                                                    bool   is_tlab) {

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

Комментарий к последней коллекции довольно показателен (и единственный, который вызывает очистку мягких ссылок)

  // If we reach this point, we're really out of memory. Try every trick
  // we can to reclaim memory. Force collection of soft references. Force
  // a complete compaction of the heap. Any additional methods for finding
  // free memory should be here, especially if they are expensive. If this
  // attempt fails, an OOM exception will be thrown.
  {
    IntFlagSetting flag_change(MarkSweepAlwaysCompactCount, 1); // Make sure the heap is fully compacted

    gch->do_collection(true             /* full */,
                       true             /* clear_all_soft_refs */,
                       size             /* size */,
                       is_tlab          /* is_tlab */,
                       number_of_generations() - 1 /* max_level */);
  }

--- Отредактировано в ответ на очевидное, я описываю слабые ссылки, а не мягкие ---

На практике я мог бы представить, что SoftReferences следуют только "не", когда JVM вызывается для сборки мусора в ответ на попытку избежать OutOfMemoryError.

Для обеспечения совместимости SoftReference со всеми четырьмя сборщиками мусора Java 1.4 и с новым сборщиком G1 решение должно приниматься только при определении достижимости. К тому времени, когда произойдут пожатия и сжатие, уже слишком поздно решать, достижим ли объект. Это предполагает (но не требует), что существует «контекст» коллекции, который определяет достижимость на основе доступности свободной памяти в куче. Такой контекст должен указывать на несоблюдение SoftReference s, прежде чем пытаться следовать им.

Поскольку OutOfMemoryError сборка мусора во избежание специально спланирована в режиме полного сбора, остановка мира, нетрудно представить сценарий, когда менеджер кучи устанавливает "не следовать SoftReference" флаг перед сбором.

--- Хорошо, поэтому я решил, что ответ "должен работать таким образом" просто недостаточно хорош ---

Из исходного кода src / share / vm / gc_implementation / concurrentMarkSweep / vmCMSOperations.cpp (основные моменты мои)

Операция, которая фактически "делает" сборку мусора:

  170 void VM_GenCollectFullConcurrent::doit() {

Лучше быть потоком виртуальной машины, иначе поток "программы" будет собирать мусор!

  171   assert(Thread::current()->is_VM_thread(), "Should be VM thread");

Мы являемся одновременным сборщиком, поэтому лучше планировать его одновременно!

  172   assert(GCLockerInvokesConcurrent || ExplicitGCInvokesConcurrent, "Unexpected");
  173 

Возьмите кучу (в которой есть объект GCCause).

  174   GenCollectedHeap* gch = GenCollectedHeap::heap();

Проверьте, нужна ли нам на переднем плане "молодая" коллекция

  175   if (_gc_count_before == gch->total_collections()) {
  176     // The "full" of do_full_collection call below "forces"
  177     // a collection; the second arg, 0, below ensures that
  178     // only the young gen is collected. XXX In the future,
  179     // we'll probably need to have something in this interface
  180     // to say do this only if we are sure we will not bail
  181     // out to a full collection in this attempt, but that's
  182     // for the future.

Потоки программы не вмешиваются в кучу?

  183     assert(SafepointSynchronize::is_at_safepoint(),
  184       "We can only be executing this arm of if at a safepoint");

Извлечь причину сборки мусора (причину этой сборки) из кучи.

  185     GCCauseSetter gccs(gch, _gc_cause);

Сделай полную коллекцию молодого космоса

Обратите внимание, что он передает значение флага must_clear_all_soft_refs кучи Который в сценарии OutOfMemory должен быть установлен в true, и в любом случае указывает "do_full_collection" не следовать мягким ссылкам

  186     gch->do_full_collection(gch->must_clear_all_soft_refs(),
  187                             0 /* collect only youngest gen */);

_gc_cause - это enum, для которого (догадка здесь) установлено значение _allocation_failure в первой попытке избежать OutOfMemoryError и _last_ditch_collection после того, как это не удастся (попытаться собрать временный мусор)

Быстрый просмотр в модуле «кучи» памяти показывает, что в do_full_collection, который вызывает do_collection мягкие ссылки, очищаются явно (при «правильных» условиях) с помощью строки

  480   ClearedAllSoftRefs casr(do_clear_all_soft_refs, collector_policy());

--- Оригинальный пост следует для тех, кто хочет узнать о слабых ссылках ---

В алгоритме Mark and Sweep мягкие ссылки не следуют из основного потока (и, следовательно, не помечаются, если другая ветвь не может достичь его через не-мягкие ссылки.

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

В основном, когда вы следите за паутиной ссылок из "основного" потока исполнения, следуют программные ссылки , а не . Это позволяет их объектам собирать мусор так, как если бы у них не было ссылок на них.

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

4 голосов
/ 04 апреля 2012

Найдена одна часть информации в FAQ по HotSpot, которая может быть устаревшей: http://www.oracle.com/technetwork/java/hotspotfaq-138619.html#gc_softrefs

Что определяет, когда объекты с мягкими ссылками сбрасываются?

Начиная с 1.3.1, легко достижимые объекты останутся живыми некоторое время после того, как в последний раз на них ссылались. значение по умолчанию - одна секунда жизни на свободный мегабайт в куче. Это значение можно изменить с помощью флага -XX: SoftRefLRUPolicyMSPerMB, который принимает целочисленные значения, представляющие миллисекунды. Например, чтобы изменить значение от одной секунды до 2,5 секунд, используйте этот флаг:

-XX: SoftRefLRUPolicyMSPerMB = 2500

Виртуальная машина Java HotSpot Server использует максимально возможный размер кучи (как установлено с параметром -Xmx) для расчета оставшегося свободного места.

Клиентская виртуальная машина Java Hotspot использует текущий размер кучи для вычисления свободное пространство.

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

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

Поведение, описанное выше, верно для 1.3.1 через Java SE 6 версии виртуальных машин Java HotSpot. Такое поведение не является частью виртуальной машины Спецификация, однако, и может быть изменена в будущих выпусках. Аналогично, флаг -XX: SoftRefLRUPolicyMSPerMB не гарантируется присутствует в любом выпуске.

До версии 1.3.1 виртуальные машины Java HotSpot очищали программные ссылки всякий раз, когда он их нашел.

Еще больше деталей доступно по адресу: http://jeremymanson.blogspot.com/2009/07/how-hotspot-decides-to-clear_07.html (любезно предоставлено комментарием MiserableVariable)

3 голосов
/ 30 марта 2012

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

Каким типом ресурсов управляет ваш кеш? Если это чистый объект, выделенный в куче, то стратегия не должна иметь значения. Однако использование ReferenceQueue может помочь вам получить уведомление, когда SoftReference очищается.

Если тип ресурса - это не только объект, выделенный для кучи, то вы должны требовать, чтобы ваши пользователи вызывали явный метод освобождения, то есть Closeable.close (). Для защиты от «забытых» вызовов этого метода выпуска вы можете рассмотреть возможность реализации метода finalize (), но остерегайтесь его побочных эффектов. Для получения дополнительной информации об этом, я рекомендую прочитать «Пункт 7: Избегайте финализаторов» из «Эффективной Java (2-е издание) Джошуа Блоха».

2 голосов
/ 04 апреля 2012

Не то чтобы это авторитетно, но используя SoftReference в гневе, я никогда не видел, чтобы ВМ сбрасывала их вместо увеличения размера ВМ.На самом деле, я почему-то предположил, что так и было, и дизайн очень зависел от этого.У меня были такие же -ms и -mx, но это не должно иметь значения.

Но я не могу найти ни одной спецификации, которая фактически говорит, что это требуется. В этом блоге , похоже, подробно рассказывается о том, как сбрасываются SoftReferences.Из быстрого чтения действительно кажется, что они могут быть очищены, даже если доступна другая память.

0 голосов
/ 04 апреля 2012

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

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

...