Как SoftReferences собираются JVM на практике?
У меня в JVM работает два отдельных кэша (один контролируется сторонней библиотекой), каждый из которых использует программные ссылки. Я бы предпочел, чтобы JVM очистил мой контролируемый кеш перед тем, который контролируется библиотекой. Javadoc SoftReference заявляет:
Все программные ссылки на объекты с мягким доступом гарантированно будут очищены до того, как виртуальная машина сгенерирует OutOfMemoryError. В противном случае никакие ограничения не накладываются на время, в которое будет очищена мягкая ссылка, или порядок, в котором будет очищен набор таких ссылок на разные объекты. Реализации виртуальных машин, тем не менее, поощряются против очистки недавно созданных или недавно использованных программных ссылок.
Прямые экземпляры этого класса могут использоваться для реализации простых кэшей; этот класс или производные подклассы могут также использоваться в больших структурах данных для реализации более сложных кэшей. Пока референт мягкой ссылки является сильно достижимым, то есть фактически используется, мягкая ссылка не будет очищена. Таким образом, сложный кэш может, например, предотвращать отбрасывание его последних использованных записей, сохраняя строгие ссылки на эти записи, оставляя оставшиеся записи для удаления по усмотрению сборщика мусора.
Как обычные реализации JVM, особенно HotSpot, обрабатывают SoftReferences на практике? Являются ли они "предвзятым отношением к очистке недавно созданных или недавно использованных мягких ссылок", как это поощряется спецификацией?
5 ответов
Похоже, что это может быть настраиваемым, но это не так. Параллельный сборщик разметки зависает в реализации кучи по умолчанию 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
до попытки следовать за ними.
поскольку OutOfMemoryError
Сборка мусора во избежание специально запланирована в режиме полного сбора, остановка мира, нетрудно представить сценарий, когда менеджер кучи устанавливает "не следовать". SoftReference
msgstr "флаг перед сбором.
--- Итак, я решил, что ответ "должен работать таким образом" просто не был достаточно хорош
Из исходного кода http://hg.openjdk.java.net/jdk7/hotspot/hotspot/file/9b0ca45cd756/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 мягкие ссылки не следуют из основного потока (и, следовательно, не помечаются, если другая ветвь не может достичь его через не мягкие ссылки.
В алгоритме копирования мягкие ссылки на Объекты не копируются (опять же, если они не достигнуты другой не мягкой ссылкой).
По сути, при просмотре веб-ссылок из "основного" потока выполнения, мягкие ссылки не отслеживаются. Это позволяет их объектам собирать мусор так, как если бы у них не было ссылок на них.
Важно отметить, что мягкие ссылки почти никогда не используются изолированно. Они обычно используются в объектах, где дизайн должен иметь несколько ссылок на объект, но только одна ссылка должна быть очищена для запуска сборки мусора (для простоты обслуживания контейнера или производительности во время выполнения без необходимости искать дорогие ссылки),
В разделе часто задаваемых вопросов HotSpot можно найти одну информацию, которая может быть устаревшей: http://www.oracle.com/technetwork/java/hotspotfaq-138619.html
Что определяет, когда объекты с мягкой ссылкой сбрасываются?
Начиная с версии 1.3.1, легко достижимые объекты будут оставаться живыми в течение некоторого времени после последнего обращения к ним. Значение по умолчанию - одна секунда жизни на свободный мегабайт в куче. Это значение можно настроить с помощью флага -XX:SoftRefLRUPolicyMSPerMB, который принимает целочисленные значения, представляющие миллисекунды. Например, чтобы изменить значение от одной секунды до 2,5 секунд, используйте этот флаг:
-XX:SoftRefLRUPolicyMSPerMB = 2500
Виртуальная машина Java HotSpot Server использует максимально возможный размер кучи (заданный параметром -Xmx) для расчета оставшегося свободного места.
Клиентская виртуальная машина Java Hotspot использует текущий размер кучи для расчета свободного места.
Это означает, что общая тенденция заключается в том, что виртуальная машина сервера увеличивает объем кучи, а не сбрасывает мягкие ссылки, и поэтому -Xmx оказывает существенное влияние на то, когда мягкие ссылки собираются мусором.
С другой стороны, клиентская виртуальная машина будет иметь большую тенденцию сбрасывать мягкие ссылки, а не увеличивать кучу.
Поведение, описанное выше, верно для версий виртуальных машин Java HotSpot от 1.3.1 до Java SE 6. Однако такое поведение не является частью спецификации VM и может быть изменено в будущих выпусках. Точно так же флаг -XX:SoftRefLRUPolicyMSPerMB не гарантированно присутствует в любом данном выпуске.
До версии 1.3.1 виртуальные машины Java HotSpot очищали программные ссылки всякий раз, когда находили их.
Более подробная информация доступна по адресу: http://jeremymanson.blogspot.com/2009/07/how-hotspot-decides-to-clear_07.html (любезно предоставлено комментарием MiserableVariable)
Каким бы ни был ответ, опора на конкретную стратегию сделает ваше программное обеспечение ненадежным, потому что каждая реализация JVM может отличаться. Даже для конкретной JVM ее настройка может изменить точную стратегию и сломать ваше программное обеспечение. Вкратце, полагаться на конкретную стратегию - ошибка.
Каким типом ресурсов управляет ваш кеш? Если объект выделен в куче, стратегия не имеет значения. Однако использование ReferenceQueue может помочь вам получить уведомление, когда SoftReference очищается.
Если тип ресурса - это не только объект, выделенный в куче, то вы должны требовать, чтобы ваши пользователи вызывали явный метод освобождения, то есть Closeable.close(). Для защиты от "забытых" вызовов этого метода выпуска вы можете рассмотреть возможность реализации метода finalize(), но остерегайтесь его побочных эффектов. Для получения дополнительной информации об этом, я рекомендую прочитать "Пункт 7: Избегайте финализаторов" из "Эффективной Java (2-е издание) Джошуа Блоха".
Не то, чтобы это авторитетно, но используя SoftReference
в гневе я никогда не видел, чтобы ВМ сбрасывала их вместо увеличения размера ВМ. На самом деле я почему-то предположил, что так и было, и дизайн очень зависел от этого. У меня было то же самое -ms
а также -mx
но это не должно иметь значения.
Но я не могу найти спецификацию, которая действительно говорит, что это требуется. Этот блог, кажется, в мельчайших подробностях того, как SoftReferences
покраснели Из быстрого чтения действительно кажется, что они могут быть очищены, даже если доступна другая память.
Просто мозговой штурм. Если вы хотите, чтобы ваш кеш очищался раньше другого кеша, может быть, вы можете связать их? Возможно, сохраняя строгую ссылку на записи во втором кэше и выпуская эти ссылки только тогда, когда очищаются члены вашего собственного кэша?
Кажется запутанным. Я бы, вероятно, склонялся к тому, чтобы просто признать, что оба кеша - это именно кеш. Промахи кэша могут ухудшить производительность, но, по крайней мере, у вашего программного обеспечения не будет сложной стратегии управления кэшем.