Подтвердить что ты не робот

Долговечные Java WeakReferences

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

  • У меня есть куча дампа с 4-дневного запуска приложения.
  • Этот кучи кучи содержит ~ 800 объектов WeakReference, которые указывают на объекты (все те же типы, которые я буду называть Foo для целей этого вопроса), сохраняя 40 МБ памяти.
  • Инструмент анализа памяти Eclipse показывает, что каждый из объектов Foo, на которые ссылаются эти WeakReferences, не ссылается ни на какие другие объекты. Мое предположение состоит в том, что это должно сделать эти объекты Foo Weakly Reachable и, следовательно, их следует собирать в следующем GC.
  • Каждый из этих объектов Foo имеет отметку времени, которая показывает, что они были распределены в течение 4-дневного прогона. У меня также есть журналы за это время, которые подтверждают, что сборка мусора происходила.
  • Огромное количество объектов Foo создается моим приложением, и только очень небольшая часть из них заканчивается в этом состоянии в дампе кучи. Это говорит мне о том, что первопричина - это какое-то состояние расы.
  • Мое приложение использует JNI для вызова в родную библиотеку. Код JNI вызывает NewGlobalRef 4 раза в начале инициализации дня, чтобы получить ссылки на классы Java, которые он использует.

Что может привести к тому, что эти классы Foo не будут собраны, несмотря на то, что ссылаются только на WeakReferences (в соответствии с Eclipse Memory Analyzer Tool)?

EDIT1:

@mindas Используемый мной метод WeakReference эквивалентен следующему примеру кода.

public class FooWeakRef extends WeakReference<Foo>
{
  public long longA;
  public long longB;
  public String stringA;

  public FooWeakRef(Foo xiObject, ReferenceQueue<Foo> xiQueue)
  {
    super(xiObject, xiQueue);
  }
}

Foo не имеет финализатора, и любой финализатор не будет рассматриваться, пока не будут очищены WeakRef. Объект не может быть финализирован, когда он слабо доступен. Подробнее см. эту страницу.

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

@jarnbjo Я ссылаюсь на WeakReference Javadoc:

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

Это говорит мне о том, что GC должен обнаруживать тот факт, что мои объекты Foo "слабо достижимы" и "в это время" очищают слабые ссылки.

ИЗМЕНИТЬ 2

@j flemm - я знаю, что 40mb звучит не так много, но я волнуюсь, что 40mb за 4 дня означает 4000mb за 100 дней. Все документы, которые я прочитал, показывают, что объекты, которые слабо доступны, не должны находиться в течение нескольких дней. Поэтому меня интересуют любые другие объяснения о том, как объект может быть сильно привязан без ссылки, отображаемой в дампе кучи.

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

ИЗМЕНИТЬ 3

@jarnbjo - я понимаю, что у меня нет гарантии, что когда JDK заметит, что объект слабо доступен. Однако я ожидал бы, что приложение под большой нагрузкой в ​​течение 4 дней предоставит достаточные возможности для GC, чтобы заметить, что мои объекты слабо доступны. Через 4 дня я очень подозрительно, что оставшиеся объекты с недостатками ссылок были как-то утечены.

ИЗМЕНИТЬ 4

@j flemm - Это действительно интересно! Чтобы уточнить, вы говорите, что GC происходит в вашем приложении и не очищает Soft/Weak refs? Можете ли вы дать мне более подробную информацию о том, что вы используете JVM + GC Config? Мое приложение использует панель памяти на 80% от кучи для запуска GC. Я предполагал, что любой GC старого поколения очистит Weak refs. Вы предполагаете, что GC собирает только слабые ссылки, когда использование памяти превышает более высокий порог? Является ли этот более высокий лимит настраиваемым?

ИЗМЕНИТЬ 5

@j flemm - Ваш комментарий об очистке WeakRef до SoftRefs соответствует Javadoc, который гласит: SoftRef: "Предположим, что сборщик мусора в определенный момент времени определяет, что объект находится в недоступном состоянии. В это время он можетвыбрать очистить атомарно все мягкие ссылки на этот объект и все мягкие ссылки на любые другие объекты, недоступные для достижения цели, через которые можно достичь через цепочку сильных ссылок. В то же время или в какой-то более поздний момент он выведет в очередь эти вновь очищенные ссылки, зарегистрированные в контрольных очередях.

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

Для ясности вы говорите, что сборщик мусора работает, когда ваше приложение имеет более 50% свободной памяти, и в этом случае он не очищает WeakRefs? Почему GC работает вообще, когда ваше приложение имеет > 50% свободной памяти? Я думаю, что ваше приложение, вероятно, просто генерирует очень малое количество мусора, и когда коллекционер работает, он очищает WeakRefs, но не SoftRefs.

ИЗМЕНИТЬ 6

@j flemm - Другое возможное объяснение поведения вашего приложения заключается в том, что молодой ген собирается, но ваши ваши слабые и мягкие ссылки все в старом гене и очищаются только тогда, когда собирается старый ген. Для моего приложения у меня есть статистика, показывающая, что собирается старый ген, что должно означать, что WeakRefs очищаются.

EDIT 7

Я начинаю щедрость по этому вопросу. Я ищу какие-либо правдоподобные объяснения того, как WeakRefs не могут быть очищены во время GC. Если ответ заключается в том, что это невозможно, я бы идеально хотел бы указать на соответствующие биты OpenJDK, которые показывают, что WeakRefs очищаются, как только объект определяется как слабо доступный, и что слабая достижимость разрешается каждый раз, когда выполняется GC.

4b9b3361

Ответ 1

Наконец-то я обзавелся проверкой исходного кода JVM Hotspot и нашел следующий код.

В referenceProcessor.cpp:

void ReferenceProcessor::process_discovered_references(
  BoolObjectClosure*           is_alive,
  OopClosure*                  keep_alive,
  VoidClosure*                 complete_gc,
  AbstractRefProcTaskExecutor* task_executor) {
  NOT_PRODUCT(verify_ok_to_handle_reflists());

  assert(!enqueuing_is_done(), "If here enqueuing should not be complete");
  // Stop treating discovered references specially.
  disable_discovery();

  bool trace_time = PrintGCDetails && PrintReferenceGC;
  // Soft references
  {
    TraceTime tt("SoftReference", trace_time, false, gclog_or_tty);
    process_discovered_reflist(_discoveredSoftRefs, _current_soft_ref_policy, true,
                               is_alive, keep_alive, complete_gc, task_executor);
  }

  update_soft_ref_master_clock();

  // Weak references
  {
    TraceTime tt("WeakReference", trace_time, false, gclog_or_tty);
    process_discovered_reflist(_discoveredWeakRefs, NULL, true,
                               is_alive, keep_alive, complete_gc, task_executor);
  }

Функция process_discovered_reflist имеет следующую подпись:

void
ReferenceProcessor::process_discovered_reflist(
  DiscoveredList               refs_lists[],
  ReferencePolicy*             policy,
  bool                         clear_referent,
  BoolObjectClosure*           is_alive,
  OopClosure*                  keep_alive,
  VoidClosure*                 complete_gc,
  AbstractRefProcTaskExecutor* task_executor)

Это показывает, что WeakRefs безоговорочно очищаются с помощью ReferenceProcessor:: process_discovered_references.

Поиск кода Hotspot для process_discovered_reference показывает, что сборщик CMS (который я использую) вызывает этот метод из следующего стека вызовов.

CMSCollector::refProcessingWork
CMSCollector::checkpointRootsFinalWork
CMSCollector::checkpointRootsFinal

Этот стек вызовов выглядит так, как будто он вызывается каждый раз при запуске коллекции CMS.

Предполагая, что это правда, единственным объяснением для долговечного слабоопределенного объекта будет либо тонкая ошибка JVM, либо если GC не был запущен.

Ответ 2

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

Ответ 3

Вам нужно уточнить, какова связь между Foo и WeakReference. Случай

class Wrapper<T> extends WeakReference<T> {
  private final T referent;
  public Wrapper(T referent) {
    super(t);
    this.referent = referent;
  }
}

сильно отличается от просто

class Wrapper<T> extends WeakReferece<T> {
  public Wrapper(T referent) {
    super(t);
  }
}

или его встроенная версия WeakReference<Foo> wr = new WeakReference<Foo>(foo).

Итак, я предполагаю, что ваш случай не так, как я описал в своем первом фрагменте кода.

Как вы уже сказали, вы работаете с JNI, вы можете проверить, есть ли у вас какие-либо небезопасные финализаторы. Каждый финализатор должен иметь finally блокировать вызов super.finalize() и легко проскальзывать.

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

Ответ 4

@iirekm No: WeakReferences "слабее", чем SoftReferences, что означает, что WeakReference всегда будет мусором, собранным до SoftReference.

Дополнительная информация в этом сообщении: Понимание классов ссылок Java: SoftReference, WeakReference и PhantomReference

Изменить: (после чтения комментариев) Да, конечно, Слабые ссылки "Слабее", чем SoftReferences, опечатка.: S

Вот некоторые примеры использования, чтобы пролить свет на объект:

  • SoftReference: кеш в памяти (объект остается в живых до тех пор, пока VM не увидит, что там не хватает кучи памяти)
  • WeakReference: Авто-очистка прослушивателей (объект должен быть очищен в следующем цикле GC после того, как он считается слабым)
  • PhantomReference. Избегайте ошибок из памяти при работе с необычно большими объектами. (Когда запланировано в ссылочной очереди, мы знаем, что объект хоста должен быть очищен, чтобы выделить другой большой объект). Подумайте об этом как о finalize() альтернативе, не имея возможности вернуть мертвые объекты к жизни (как вы могли бы с финализацией)

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

Это лучший ресурс, который я смог найти по этому вопросу: http://www.pawlan.com/monica/articles/refobjs/

Изменить 2: Добавлен "быть" перед очисткой в ​​PhantomRef

Ответ 5

Я не знаком с Java, но вы можете использовать коллективный сборщик мусора, который будет содержать только ваши объекты Foo и FooWeakRef (не собрано), пока

  • они прошли в старшем поколении
  • достаточно памяти для размещения новых объектов в младших поколениях

Знает ли журнал, указывающий на то, что сбор мусора различает основные и второстепенные коллекции?

Ответ 6

Для неверующих, которые утверждают, что слабые ссылки очищаются до мягких ссылок:

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;


public class Test {

/**
 * @param args
 */
public static void main(String[] args) {
    ReferenceQueue<Object> q = new ReferenceQueue<Object>();
    Map<Reference<?>, String> referenceToId = new HashMap<Reference<?>, String>();
    for(int i=0; i<100; ++i) {
        Object obj = new byte [10*1024*1024];    // 10M
        SoftReference<Object> sr = new SoftReference<Object>(obj, q);
        referenceToId.put(sr, "soft:"+i);
        WeakReference<Object> wr = new WeakReference<Object>(obj, q);
        referenceToId.put(wr, "weak:"+i);

        for(;;){
            Reference<?> ref = q.poll();
            if(ref == null) {
                break;
            }
            System.out.println("cleared reference " + referenceToId.get(ref) + ", value=" + ref.get());
        }
    }
}
}

Если вы запустите его с помощью -client или -server, вы увидите, что мягкие ссылки всегда очищаются до слабых ссылок, что также согласуется с Javadoc: http://download.oracle.com/javase/1.4.2/docs/api/java/lang/ref/package-summary.html#reachability

Типично мягкие/слабые ссылки используются в связи с Картами, чтобы создавать типы кешей. Если ключи в вашей карте сравниваются с оператором == (или unoverriden.equals from Object), то лучше всего использовать Map, которая работает с ключами SoftReference (например, из Apache Commons) - когда объект "исчезает", ни один другой объект никогда не будет быть равным в "==" смысле старой. Если ключи вашей Карты сравниваются с расширенным оператором .equals(), например String или Date, многие другие объекты могут соответствовать "исчезающему", поэтому лучше использовать стандартный WeakHashMap.

Ответ 7

Попробуйте SoftReference. Javadoc говорит: "Все мягкие ссылки на объекты с мягким достижением гарантированно будут очищены до того, как виртуальная машина выбросит OutOfMemoryError.

WeakReference не имеет таких гарантий, что делает их более подходящими для кешей, но иногда SoftReferences лучше.