Вопрос

Я использую библиотеку поиска, которая рекомендует держать объект дескриптора поиска открытым, поскольку это может принести пользу кэшу запросов.Со временем я заметил, что кэш имеет тенденцию раздуваться (на несколько сотен мегабайт и продолжает расти), и начали появляться ООМ.Нет никакого способа обеспечить соблюдение ограничений этого кэша или спланировать, сколько памяти он может использовать.Таким образом, я увеличил Xmx предел, но это лишь временное решение проблемы.

В конце концов, я подумываю о том, чтобы сделать этот объект referent из java.lang.ref.SoftReference.Таким образом, если системе не хватает свободной памяти, она отпустит объект, и новый будет создан по требованию.Это привело бы к некоторому снижению скорости после нового старта, но это гораздо лучшая альтернатива, чем попадание в ООМ.

Единственная проблема, которую я вижу в SoftReferences, заключается в том, что нет чистого способа доработать их ссылки.В моем случае, прежде чем уничтожить дескриптор поиска, мне нужно закрыть его, иначе в системе могут закончиться файловые дескрипторы.Очевидно, что я могу обернуть этот дескриптор в другой объект, написать на нем финализатор (или привязать к ReferenceQueue / PhantomReference) и отпустить.Но, эй, каждая отдельная статья на этой планете рекомендует не использовать финализаторы, и особенно - против финализаторов для освобождения дескрипторов файлов (например Эффективная Java изд.II, стр. 27.).

Так что я несколько озадачен.Должен ли я тщательно игнорировать все эти советы и идти дальше?В противном случае, есть ли какие-либо другие жизнеспособные альтернативы?Заранее благодарю.

ПРАВКА №1:Приведенный ниже текст был добавлен после тестирования некоторого кода, предложенного Томом Хотином.Мне кажется, что либо предложение не работает, либо я что-то упускаю.Вот код:

class Bloat {  // just a heap filler really
   private double a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;

   private final int ii;

   public Bloat(final int ii) {
      this.ii = ii;
   }
}

// as recommended by Tom Hawtin
class MyReference<T> extends SoftReference<T> {
   private final T hardRef;

   MyReference(T referent, ReferenceQueue<? super T> q) {
      super(referent, q);
      this.hardRef = referent;
   }
}

//...meanwhile, somewhere in the neighbouring galaxy...
{
   ReferenceQueue<Bloat> rq = new ReferenceQueue<Bloat>();
   Set<SoftReference<Bloat>> set = new HashSet<SoftReference<Bloat>>();
   int i=0;

   while(i<50000) {
//      set.add(new MyReference<Bloat>(new Bloat(i), rq));
      set.add(new SoftReference<Bloat>(new Bloat(i), rq));

//      MyReference<Bloat> polled = (MyReference<Bloat>) rq.poll();
      SoftReference<Bloat> polled = (SoftReference<Bloat>) rq.poll();

      if (polled != null) {
         Bloat polledBloat = polled.get();
         if (polledBloat == null) {
           System.out.println("is null :(");
         } else {
           System.out.println("is not null!");
         }
      }
      i++;
   }
}

Если я запущу приведенный выше фрагмент с помощью -Xmx10m и SoftReferences (как в коде выше), я получаю тонны is null :( напечатано.Но если я заменю код на MyReference (раскомментировав две строки с помощью MyReference и закомментировав их с помощью SoftReference) Я всегда получаю OOM.

Как я понял из совета, имея жесткую ссылку внутри MyReference не должно препятствовать попаданию объекта ReferenceQueue, верно?

Это было полезно?

Решение

Ответ Томса правильный, однако код, добавленный к вопросу, не совпадает с предложенным Томом. То, что предлагал Том, выглядит примерно так:

class Bloat {  // just a heap filler really
    public Reader res;
    private double a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;

    private final int ii;

    public Bloat(final int ii, Reader res) {
       this.ii = ii;
       this.res = res;
    }
 }

 // as recommended by Tom Hawtin
 class MySoftBloatReference extends SoftReference<Bloat> {
    public final Reader hardRef;

    MySoftBloatReference(Bloat referent, ReferenceQueue<Bloat> q) {
       super(referent, q);
       this.hardRef = referent.res;
    }
 }

 //...meanwhile, somewhere in the neighbouring galaxy...
 {
    ReferenceQueue<Bloat> rq = new ReferenceQueue<Bloat>();
    Set<SoftReference<Bloat>> set = new HashSet<SoftReference<Bloat>>();
    int i=0;

    while(i<50000) {
        set.add(new MySoftBloatReference(new Bloat(i, new StringReader("test")), rq));

        MySoftBloatReference polled = (MySoftBloatReference) rq.poll();

        if (polled != null) {
            // close the reference that we are holding on to
            try {
                polled.hardRef.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        i++;
    }
}

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

Тем не менее, если вы держите открытые ссылки на файлы, вы рискуете исчерпать их, прежде чем исчерпаете память. Возможно, вы также захотите иметь кэш LRU, чтобы держать не более 500 открытых файлов втыкающим пальцем . Они также могут относиться к типу MyReference, чтобы при необходимости их можно было собирать мусором.

Чтобы пояснить, как работает MySoftBloatReference, базовый класс, то есть SoftReference, все еще содержит ссылку на объект, занимающий всю память. Это объект, который необходимо освободить, чтобы предотвратить появление OOM. Однако, если объект освобожден, вам все равно нужно освободить ресурсы, которые использует Bloat, то есть Bloat использует два типа ресурсов, память и дескриптор файла, оба эти ресурса должны быть освобождены, или вы запускаете из одного или другого ресурса. SoftReference справляется с нагрузкой на ресурс памяти, освобождая этот объект, однако вам также необходимо освободить другой ресурс, дескриптор файла. Поскольку Bloat уже был освобожден, мы не можем использовать его для освобождения соответствующего ресурса, поэтому MySoftBloatReference хранит жесткую ссылку на внутренний ресурс, который необходимо закрыть. Как только будет сообщено, что Bloat освобожден, то есть, когда ссылка появится в ReferenceQueue, MySoftBloatReference также может закрыть связанный ресурс через жесткую ссылку, которая у него есть.

РЕДАКТИРОВАТЬ: Обновил код, чтобы он компилировался при добавлении в класс. Он использует StringReader, чтобы проиллюстрировать концепцию закрытия Reader, который используется для представления внешнего ресурса, который необходимо освободить. В этом конкретном случае закрытие этого потока по сути является запретом и поэтому не требуется, но показывает, как это сделать, если это необходимо.

Другие советы

Для конечного числа ресурсов: подкласс SoftReference . Мягкая ссылка должна указывать на охватывающий объект. Сильная ссылка в подклассе должна ссылаться на ресурс, поэтому он всегда сильно доступен. При чтении через ReferenceQueue poll ресурс может быть закрыт и удален из кэша. Кэш должен быть освобожден правильно (если сам SoftReference собирает мусор, его нельзя ставить в очередь в ReferenceQueue ).

Будьте осторожны, если у вас есть только конечное количество ресурсов, не выпущенных в кеш - исключите старые записи (действительно, вы можете отбросить мягкие ссылки с помощью кеша if finite, если это подходит вашей ситуации). Обычно это тот случай, когда важнее ресурс без памяти, и в этом случае кеша удаления LRU без экзотических эталонных объектов должно быть достаточно.

(Мой ответ # 1000. Отправлено из лондонского DevDay.)

Хм.
(Насколько я знаю) Вы не можете держать палку за оба конца.Либо вы придерживаетесь своей информации, либо отпускаете ее.
Однако...вы можете сохранить некоторую ключевую информацию, которая позволила бы вам завершить работу.Конечно, ключевая информация должна быть значительно меньше "реальной информации" и не должна содержать реальной информации в графе достижимых объектов (слабые ссылки могут помочь вам в этом).
Опираясь на существующий пример (обратите внимание на ключевое информационное поле):

public class Test1 {
    static class Bloat {  // just a heap filler really
        private double a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z;

        private final int ii;

        public Bloat(final int ii) {
            this.ii = ii;
        }
    }

    // as recommended by Tom Hawtin
    static class MyReference<T, K> extends SoftReference<T> {
        private final K keyInformation;

        MyReference(T referent, K keyInformation, ReferenceQueue<? super T> q) {
            super(referent, q);
            this.keyInformation = keyInformation;
        }

        public K getKeyInformation() {
            return keyInformation;
        }
    }

    //...meanwhile, somewhere in the neighbouring galaxy...
    public static void main(String[] args) throws InterruptedException {
        ReferenceQueue<Bloat> rq = new ReferenceQueue<Bloat>();
        Set<SoftReference<Bloat>> set = new HashSet<SoftReference<Bloat>>();
        int i = 0;

        while (i < 50000) {
            set.add(new MyReference<Bloat, Integer>(new Bloat(i), i, rq));

            final Reference<? extends Bloat> polled = rq.poll();

            if (polled != null) {
                if (polled instanceof MyReference) {
                    final Object keyInfo = ((MyReference) polled).getKeyInformation();
                    System.out.println("not null, got key info: " + keyInfo + ", finalizing...");
                } else {
                    System.out.println("null, can't finalize.");
                }
                rq.remove();
                System.out.println("removed reference");
            }

Редактировать:
Я хочу подробнее остановиться на "либо придержите свою информацию, либо отпустите ее".Предполагая, что у вас был какой-то способ сохранить свою информацию.Это вынудило бы GC снять пометку с ваших данных, в результате чего данные фактически были бы очищены только после того, как вы закончите с ними, во втором цикле GC.Это возможно - и это именно то, для чего предназначена finalize().Поскольку вы заявили, что не хотите, чтобы происходил второй цикл, вы не можете хранить свою информацию (если a-> b, то !b->!a).а это значит, что вы должны отпустить это.

Редактировать 2:
На самом деле, произошел бы второй цикл - но для ваших "ключевых данных", а не для ваших "основных данных раздувания".Фактические данные будут удалены в первом цикле.

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

@Paul - большое спасибо за ответ и разъяснения.

@Ran - Я думаю, что в вашем текущем коде i ++ отсутствует в конце цикла.Кроме того, вам не нужно выполнять rq.remove() в цикле, поскольку rq.poll() уже удаляет верхнюю ссылку, не так ли?

Несколько пунктов:

1) Мне пришлось добавить оператор Thread.sleep(1) после i ++ в цикле (для обоих решений Paul и Ran), чтобы избежать ООМ, но это не имеет отношения к общей картине, а также зависит от платформы.Моя машина оснащена четырехъядерным процессором и работает под управлением Sun Linux 1.6.0_16 JDK.

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

  • нет никакой гарантии, что финализаторы будут выполнены быстро, поэтому никогда не делайте ничего критичного по времени в финализаторе - также нет никаких гарантий для SoftRererences!
  • Никогда не зависите от финализатора для обновления критического постоянного состояния - я не
  • использование финализаторов приводит к серьезному снижению производительности - в моем худшем случае я бы завершал работу примерно с одним объектом в минуту или около того.Я думаю, что смогу с этим жить.
  • используйте try /finally - о да, я обязательно это сделаю!

Необходимость создавать огромное количество каркасов только для того, что кажется простой задачей, мне не кажется разумной.Я имею в виду, буквально, что скорость WTF в минуту была бы довольно высокой для любого другого, кто просматривает такой код.

3) К сожалению, нет возможности разделить очки между Полом, Томом и Рэном : ( Надеюсь, Том не будет возражать, поскольку у него их уже много :) Судить между Полом и Рэном было намного сложнее - я думаю, что оба ответа работают и верны.Я устанавливаю флаг accept для ответа Пола только потому, что он был оценен выше (и имеет более подробное объяснение), но решение Ran совсем не плохое и, вероятно, было бы моим выбором, если бы я решил реализовать его с помощью SoftReferences.Спасибо, ребята!

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top