Использование SoftReference для статических данных для предотвращения нехватки памяти в Java

У меня есть класс со статическим членом, например:

class C
{
  static Map m=new HashMap();
  {
    ... initialize the map with some values ...
  }
}

Насколько я знаю, это потребовало бы памяти практически до конца программы. Мне было интересно, смогу ли я решить это с помощью мягких ссылок, например:

class C
{
  static volatile SoftReference<Map> m=null;
  static Map getM() {
    Map ret;
    if(m == null || (ret = m.get()) == null) {
      ret=new HashMap();
      ... initialize the map ...
      m=new SoftReference(ret);
    }
    return ret;
  }
}

Вопрос в том

  1. правильный ли этот подход (и реализация)?
  2. если да, то окупается ли это в реальных ситуациях?

person jpalecek    schedule 12.07.2009    source источник


Ответы (7)


Во-первых, приведенный выше код не является потокобезопасным.

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

  1. Достаточно большой, чтобы их использование памяти имело значение
  2. Возможность воссоздания на лету без недопустимых задержек
  3. Используется только в тех случаях, когда другим частям программы требуется меньше памяти - в противном случае максимальная требуемая память была бы такой же, только среднее значение было бы меньше, и вы, вероятно, даже не увидели бы это вне JVM, поскольку он возвращает память кучи для ОС очень неохотно.

Здесь 1. и 2. как бы противоречат друг другу - большие объекты также требуют больше времени для создания.

person Michael Borgwardt    schedule 12.07.2009

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

person Peter Lawrey    schedule 12.07.2009

getM() должно быть synchronized, чтобы избежать одновременной инициализации m разными потоками.

person Bob    schedule 12.07.2009
comment
@jpalecek: Возникает вопрос, правильный ли этот подход (и реализация)? Это полностью открытый вариант, и можно учитывать безопасность потоков. - person erickson; 13.07.2009

Насколько большой будет эта карта? Стоит ли пытаться справиться с этим таким образом? Измеряли ли вы потребление памяти этим (для чего это стоит, я считаю, что вышеизложенное в целом нормально, но мой первый вопрос об оптимизации: «Что это действительно спасает меня»).

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

Я бы синхронизировал вышеописанное на случай, если карта будет собирать мусор и два потока одновременно попадут в getMap(). В противном случае вы будете создавать две карты одновременно!

person Brian Agnew    schedule 12.07.2009
comment
Создание двух карт одновременно не является реальной проблемой. Один будет выброшен. Однако проблема с многопоточностью может привести к бесконечным циклам (как правило, плохо с точки зрения производительности). Кроме того, его синхронизация может привести к очень плохой производительности, если на многопоточных машинах возникнут серьезные споры. - person Tom Hawtin - tackline; 12.07.2009
comment
Разве проблема с двумя картами не заключается просто в том, что 2 (или более) будут существовать одновременно, и они были идентифицированы как весьма значительные? - person Brian Agnew; 12.07.2009

Возможно, вы ищете WeakHashMap ? Затем записи на карте можно собирать отдельно.

Хотя по моему опыту это мало помогло, поэтому вместо этого я создал кэш LRU, используя LinkedHashMap. Преимущество в том, что я могу контролировать размер, чтобы он не был слишком большим и при этом оставался полезным.

person starblue    schedule 12.07.2009
comment
Проблема с WeakHashMap заключается в том, что слабые ссылки связаны с ключами, а не со значениями. Это полезно для таких вещей, как сохранение данных в HttpSession, но менее полезно для кеша. Также обратите внимание, что JVM будет собирать объект со слабыми ссылками сразу же, когда нет сильных ссылок, тогда как он будет собирать SoftReference только при нехватке памяти. - person oxbow_lakes; 12.07.2009
comment
Ага, агрессивная сборка мусора объясняет, почему это не сработало. К сожалению, стандартного SoftHashMap не существует. - person starblue; 12.07.2009
comment
WeakReference действительно опасны для кэшей. NetBeans, по моему опыту, после некоторого использования просто сидит с ЦП на 100%, выполняя доступ к файлам, не касаясь жесткого диска. Он использует кэш WeakHashMap, который в какой-то момент оптимизируется HotSpot для удаления сразу после кэширования. Вероятно, стоит убедиться, что у вас есть какая-то толковая производительность без кеша. - person Tom Hawtin - tackline; 12.07.2009

Мне было интересно, смогу ли я решить это с помощью мягких ссылок

Что это вы пытаетесь решить? У вас проблемы с памятью или вы преждевременно оптимизируете?

В любом слючае,

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

  2. Причина использования SoftReferences состоит в том, чтобы избежать нехватки памяти, поскольку нет другого контракта, кроме того, что они будут очищены до того, как виртуальная машина выдаст OutOfMemoryError. Поэтому у этого подхода нет никаких гарантированных преимуществ, кроме того, что кэш не создается до тех пор, пока он не будет использован впервые.

person akf    schedule 12.07.2009

Первое, что я замечаю в коде, это то, что он смешивает общие типы с необработанными. Это просто приведет к беспорядку. javac в JDK7 имеет -Xlint:rawtypes для быстрого обнаружения такого рода ошибок до того, как начнутся проблемы.

Код не является потокобезопасным, но использует статику, поэтому публикуется во всех потоках. Вы, вероятно, не хотите, чтобы это было synchronized, потому что это вызовет проблемы, если состязаться на многопоточных машинах.

Проблема с использованием SoftReference для всего кеша заключается в том, что вы вызовете всплески, когда ссылка будет очищена. В некоторых случаях может быть лучше иметь ThreadLocal<SoftReference<Map<K,V>>>, который будет распространять пики и обеспечивать безопасность потока помощи за счет отсутствия совместного использования потоков между потоками.

Однако создать более умный кеш сложнее. Часто вы получаете значения, ссылающиеся на ключи. Есть способы обойти этот бит, это беспорядок. Я не думаю, что эфемероны (по сути, пара связанных Reference) сделают JDK7. Вы можете найти коллекции Google, на которые стоит обратить внимание (хотя я этого не делал).

java.util.LinkedHashMap дает простой способ ограничить количество кешированных записей, но не очень полезен, если вы не можете быть уверены, насколько велики записи, и может вызвать проблемы, если он останавливает сбор больших объектных систем, таких как ClassLoaders. Некоторые люди говорят, что вы не должны оставлять вытеснение кеша на усмотрение сборщика мусора, но другие говорят, что вам не следует использовать сборщик мусора.

person Tom Hawtin - tackline    schedule 12.07.2009