Как оценить, имеет ли JVM достаточную свободную память для конкретной структуры данных?

У меня есть следующая ситуация: есть несколько машин, образующих кластер. Клиенты могут загружать наборы данных, и нам нужно выбрать node, на котором будет загружен набор данных, и отказаться от загрузки/исключения ошибки OOM, если нет машины, которая могла бы соответствовать набору данных.

Что мы делаем сейчас: теперь entry count в наборе данных и оцениваем memory to be used как entry count * empirical factor (определяется вручную). Затем проверьте, меньше ли это свободной памяти (полученной Runtime.freeMemory()), и если да, загрузите ее (в противном случае повторите процесс на других узлах/сообщите, что свободной емкости нет).

Проблемы с этим подходом:

  • empirical factor необходимо повторно просмотреть и обновить вручную
  • freeMemory иногда может занижать отчет из-за некоторого не очищенного мусора (чего можно избежать, выполнив System.gc перед каждым таким вызовом, однако это замедлит работу сервера, а также потенциально может привести к преждевременному продвижению)
  • альтернативой было бы "просто попытаться загрузить набор данных" (и вернуться, если OOM будет выброшен), однако после того, как OOM будет сброшен, вы потенциально повредили другие потоки, запущенные в одной JVM, и нет изящного способа оправившись от него.

Есть ли лучшие решения этой проблемы?

Ответ 1

empirical factor можно вычислить как шаг сборки и поместить в файл свойств.

В то время как freeMemory() почти всегда меньше суммы, свободной после GC, вы можете проверить ее, если она доступна, и вызвать System.gc(), если maxMemory() указывает, что может быть много.

ПРИМЕЧАНИЕ. Использование System.gc() в производстве делает только очень редкие ситуации и, в общем, часто неправильно используется, что приводит к снижению производительности и скрывая реальную проблему.

Я бы не запускал OOME, если вы не запускаете JVM, которую вы можете перезапустить по мере необходимости.

Ответ 2

Мое решение:

  • Установите Xmx как 90%-95% ОЗУ физического компьютера, если другой процесс не запущен, кроме вашей программы. Для устройства с 32 ГБ оперативной памяти установите Xmx как 27MB - 28MB.

  • Используйте один из хороших gc-алгоритмов - CMS или G1GC и настройте соответствующие параметры. I prefer G1GC if you need more than 4 GB RAM for your application. Обратите внимание на этот вопрос, если вы выбрали G1GC:

    Агрессивная стратегия сборщика мусора

    Сокращение времени паузы JVM > 1 секунда с использованием UseConcMarkSweepGC

  • Вычислить Cap для использования памяти самостоятельно, а не проверять свободную память. Добавьте выделенную память и память. Subtract it from your own cap like 90% of Xmx. Если у вас все еще есть доступная память, укажите запрос на выделение памяти.

Ответ 3

Альтернативный подход заключается в том, чтобы изолировать каждую нагрузку данных в своей собственной JVM. Вы просто предопределяете максимальный размер кучи JVM и т.д. И устанавливаете количество JVM для каждого хоста таким образом, чтобы каждая JVM могла принимать максимальный размер кучи. Это будет использовать немного больше ресурсов — это означает, что вы не можете использовать каждый последний байт памяти путем переполнения в более низких данных-нагрузках данных; но это значительно упрощает проблему (и снижает риск ошибочного), она позволяет рассказать, когда/нужно ли вам добавлять новые хосты, и, что самое главное, уменьшает влияние, которое может оказать любой клиент на всех остальных клиентов.

При таком подходе данный JVM либо "занят", либо "доступен".

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

Ответ 4

альтернативой было бы "просто попытаться загрузить набор данных" (и вернуться, если OOM будет сброшен), однако после того, как будет выпущен OOM, вы потенциально повредили другие потоки, запущенные в одной JVM, и нет изящного способа оправившись от него.

Невозможно обработать и восстановить OOME в JVM, но есть способ реагировать на до. Java имеет java.lang.ref.SoftReference, который, как гарантируется, был очищен до того, как виртуальная машина выбрасывает OutOfMemoryError. Этот факт может быть использован для раннего прогнозирования OOM. Например, загрузка данных может быть прервана при запуске прогнозирования.

    ReferenceQueue<Object> q = new ReferenceQueue<>();
    SoftReference<Object> reference = new SoftReference<>(new Object(), q);
    q.remove();
    // reference removed - stop data load immediately

Чувствительность может быть настроена с -XX: флаг SoftRefLRUPolicyMSPerMB (для Oracle JVM). Решение не идеальное, эффективность зависит от разных факторов - используйте другие полезные ссылки, используемые в коде, как настроено GC, версия JVM, погода на Марсе... Но это может помочь, если вам повезет.

Ответ 5

Как вы правильно отметили, использование freeMemory не укажет объем памяти, который может быть освобожден Java Garbage Collection. Вы можете запускать тесты нагрузки и понимать шаблон использования кучи JVM и распределение памяти, шаблон выделения, используя такие инструменты, как JConsole, VisualVM, jstat и printGCStats для JVM. Это даст более точную информацию о вычислении empirical factor, в основном поймите, какой шаблон загрузки может обрабатывать ваше приложение Java. Следующим шагом было бы выбрать правильный GC и настроить основные настройки GC для повышения эффективности. Это не быстрое решение, но, возможно, в долгосрочном плане лучшее решение.

Другой способ убить вашу JVM с помощью -XX: OnOutOfMemoryError = "kill -9% p" Настройка JVM, как только OOM произойдет, а затем напишите, resue простой процесс мониторинга script, чтобы вызвать вашу JVM, если она не работает.

Ответ 6

Клиенты могут загружать наборы данных, и нам нужно выбрать node, на котором набор данных будет загружен и откажется загружать/исключать ошибку OOM, если нет никакой машины, которая могла бы соответствовать набору данных.

Это задача планирования заданий, т.е. У меня есть конечные ресурсы, как мы можем их наилучшим образом использовать. Проблема с OOM появится ближе к концу.

У нас есть один из основных факторов, то есть ОЗУ, но решения задач планирования зависят от многих факторов, т.е....

  • Являются ли задания маленькими или большими, т.е. существуют сотни/тысячи этих запусков на node или два или три. Подумайте о планировщике Linux.

  • Нужно ли заполнять их в определенные временные рамки? Планировщик реального времени.

Учитывая все, что мы знаем в начале работы, можем ли мы предсказать, когда работа закончится с некоторыми временными рамками? Если мы сможем предсказать, что на node X мы освобождаем 100 МБ каждые 15-20 секунд, у нас есть способ запланировать 200 Мб работы на этом node, то есть я уверен, что через 40 секунд я завершу 200 Мб пространства на этом node и 40 секунд - это приемлемый предел для человека или машины, отправляющей задание.

Предположим, что мы имеем функцию следующим образом.

predicted_time predict(long bytes[, factors]); 

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

Факторы будут задаваться весами при расчете predicted_time.

predicted_time - это число миллисекунд (может быть любым TimeUnit), с которого этот node полагает, что теперь он может обслуживать эту задачу, node, дающий вам наименьшее число, является node, задание должно быть запланировано. Затем вы можете использовать эту функцию следующим образом, где у нас есть очередь задач, т.е. В следующем коде this.nodes[i] представляет экземпляр JVM.

private void scheduleTask() {
  while(WorkEvent()) {
        while(!this.queue.isEmpty()) {
            Task t = this.queue.poll();
            for (int i = 0; i < this.maxNodes; i++) {
                long predicted_time = this.nodes[i].predict(t);
                if (predicted_time < 0) {
                    boolean b = this.queue.offer(t);
                    assert(b);
                    break;
                }
                if (predicted_time <= USER_EXPERIENCE_DELAY) {
                    this.nodes[i].addTask(t);
                    break;
                }
                alert_user(boolean b = this.queue.offer(t);
                assert(b);
            }
        }
    }
}

Если predicted_time < 0 у нас есть ошибка, мы перепланируем работу, на самом деле мы хотели бы знать, почему, но это не сложно добавить. Если predicted_time <= USER_EXPERIENCE_DELAY задание может быть запланировано.

Как это избежать OOM

Мы можем собрать любую статистику, которую хотим от нашего планировщика, то есть, сколько заданий размера X, где запланировано правильно, целью было бы уменьшить ошибки и сделать их более надежными с течением времени, то есть сократить количество времени, которое мы скажем клиенту что их работу нельзя обслуживать. То, что мы сделали, сводит проблему к чему-то, что мы можем статистически улучшить для оптимального решения.

Ответ 7

Клиенты могут загружать наборы данных, и нам нужно выбрать node, на котором набор данных будет загружен и откажется загружать/исключать ошибку OOM, если нет никакой машины, которая могла бы соответствовать набору данных.

Это задача планирования заданий, т.е. У меня есть конечные ресурсы, как мы можем их наилучшим образом использовать. Проблема с OOM появится ближе к концу.

У нас есть один из основных факторов, то есть ОЗУ, но решения задач планирования зависят от многих факторов, т.е....

  • Являются ли задания маленькими или большими, т.е. существуют сотни/тысячи этих запусков на node или два или три. Подумайте о планировщике Linux.

  • Нужно ли заполнять их в определенные временные рамки? Планировщик реального времени.

Учитывая все, что мы знаем в начале работы, можем ли мы предсказать, когда работа закончится в течение некоторого периода времени? Если мы сможем предсказать, что на node X мы освобождаем 100 МБ каждые 15-20 секунд, у нас есть способ запланировать 200 Мб работы на этом node, то есть я уверен, что через 40 секунд я завершу 200 Мб пространства на этом node и 40 секунд - это приемлемый предел для человека или машины, отправляющей задание.

Предположим, что мы имеем функцию следующим образом.

predicted_time predict(long bytes[, factors]); 

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

Факторы будут задаваться весами при расчете predicted_time.

predicted_time - это число миллисекунд (может быть любым TimeUnit), с которого этот node полагает, что теперь он может обслуживать эту задачу, node, дающий вам наименьшее число, является node, задание должно быть запланировано. Затем вы можете использовать эту функцию следующим образом, где у нас есть очередь задач, т.е. В следующем коде this.nodes[i] представляет экземпляр JVM.

private void scheduleTask() {
  while(WorkEvent()) {
        while(!this.queue.isEmpty()) {
            Task t = this.queue.poll();
            for (int i = 0; i < this.maxNodes; i++) {
                long predicted_time = this.nodes[i].predict(t);
                if (predicted_time < 0) {
                    boolean b = this.queue.offer(t);
                    assert(b);
                    break;
                }
                if (predicted_time <= USER_EXPERIENCE_DELAY) {
                    this.nodes[i].addTask(t);
                    break;
                }
                alert_user(boolean b = this.queue.offer(t);
                assert(b);
            }
        }
    }
}

Если predicted_time < 0 у нас есть ошибка, мы перепланируем работу, на самом деле мы хотели бы знать, почему, но это не сложно добавить. Если predicted_time <= USER_EXPERIENCE_DELAY задание может быть запланировано.

Как это избежать OOM

Мы можем собрать любую статистику, которую хотим от нашего планировщика, то есть, сколько заданий размера X, где запланировано правильно, целью было бы уменьшить ошибки и сделать их более надежными с течением времени, то есть сократить количество времени, которое мы скажем клиенту что их работа не может быть обслужена или она не удалась. То, что мы сделали или, по крайней мере, пытаемся попытаться, состоит в том, чтобы свести проблему к чему-то, что мы можем статистически улучшить для оптимального решения.