Полезный показатель для определения того, когда JVM собирается попасть в память/GC-проблема

У меня есть приложение для обработки данных scala, которое 95% времени может обрабатывать данные, брошенные в него в памяти. Остальные 5%, если их не остановить, обычно не попадают в OutOfMemoryError, а просто попадают в цикл основных GC, который всплескивает процессор, предотвращает выполнение фоновых потоков и, если он даже заканчивается, занимает 10x-50x, если он имеет достаточно памяти.

Я реализовал систему, которая может сбрасывать данные на диск и обрабатывать поток диска, как если бы он был итератором в памяти. Он обычно на порядок медленнее памяти, но достаточно для этих 5% случаев. В настоящее время я запускаю эвристику максимального размера контекста коллекции, который отслеживает размер различных коллекций, участвующих в обработке данных. Это работает, но на самом деле это всего лишь эмпирический порог adhoc.

Я бы скорее отреагировал на JVM, приблизившись к вышеупомянутому плохому состоянию и сбросившись на диск в то время. Я пробовал наблюдать за памятью, но не могу найти правильную комбинацию eden, old и т.д., Чтобы надежно предсказать смертельную спираль. Я также пытался просто наблюдать за частотой основных GC, но, похоже, также страдает от того, что у вас слишком широкий "слишком консервативный", чтобы "слишком поздно".

Будут оценены любые ресурсы для оценки состояния здоровья JVM и выявления проблемных состояний.

Ответ 1

Один надежный способ - зарегистрировать прослушиватель уведомлений о событиях GC и проверить работоспособность памяти после всех событий GC. Непосредственно после полного GC-события используемая память представляет собой ваш фактический живой набор данных. Если вы в этот момент времени свободны от свободной памяти, вероятно, время начнет мигать с диском.

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

Вы можете зарегистрировать прослушиватель уведомлений и обрабатывать полные события GC, используя что-то вроде следующего кода:

// ... standard imports ommitted
import com.sun.management.GarbageCollectionNotificationInfo;

public static void installGCMonitoring() {
    List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
    for (GarbageCollectorMXBean gcBean : gcBeans) {
        NotificationEmitter emitter = (NotificationEmitter) gcBean;
        NotificationListener listener = notificationListener();
        emitter.addNotificationListener(listener, null, null);
    }
}

private static NotificationListener notificationListener() {
    return new NotificationListener() {
        @Override
        public void handleNotification(Notification notification, Object handback) {
            if (notification.getType()
                    .equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION)) {
                GarbageCollectionNotificationInfo info = GarbageCollectionNotificationInfo
                        .from((CompositeData) notification.getUserData());
                String gctype = info.getGcAction();
                if (gctype.contains("major")) {
                    // We are only interested in full (major) GCs
                    Map<String, MemoryUsage> mem = info.getGcInfo().getMemoryUsageAfterGc();
                    for (Entry<String, MemoryUsage> entry : mem.entrySet()) {
                        String memoryPoolName = entry.getKey();
                        MemoryUsage memdetail = entry.getValue();
                        long memMax = memdetail.getMax();
                        long memUsed = memdetail.getUsed();
                        // Use the memMax/memUsed of the pool you are interested in (probably old gen)
                        // to determine memory health.
                    }
                }
            }
        }
    };
}

Подтвердите эту статью, в которой мы впервые получили эту идею.

Ответ 2

В дополнение к механизмам уведомления MemoryMXBean, описанным в ссылке @Alla . Вы можете использовать комбинацию слабых ссылок и очередей ссылок. Эта старая, но действительная статья содержит хорошее описание слабых, мягких и phantom ссылок и ссылочных очередей.

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

Ответ 3

может быть эта ссылка поможет вам http://www.javaspecialists.eu/archive/Issue092.html

В моей MemoryWarningSystem вы добавляете слушатели, реализующие интерфейс MemoryWarningSystem.Listener, с одним методом memoryUsageLow(long usedMemory, long maxMemory), который будет вызываться, когда достигнут порог. В моих экспериментах память bean уведомляет нас об этом вскоре после того, как превышен порог использования, но я не смог определить степень детализации. Следует отметить, что слушатель вызывается специальным потоком, называемым потоком детектора низкой памяти, который теперь является частью стандартного JVM.

Каков порог? И какой из многих бассейнов мы должны контролировать? Единственный разумный пул для мониторинга - это теневое поколение (старое пространство). Когда вы устанавливаете размер памяти с -Xmx256m, вы устанавливаете максимальную память, которая будет использоваться в Tenured Generation.