Как заранее узнать об ошибках OutOfMemory или StackOverflow - PullRequest
8 голосов
/ 27 апреля 2009

Есть ли в Java способ узнать, что ошибка StackOverflow или исключение OutOfMemory могут произойти в ближайшее время?

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

Для ошибки StackOverflow существует ли способ получить глубину рекурсии и как узнать, какое значение глубины рекурсии может вызвать ошибку?

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

Ответы [ 12 ]

8 голосов
/ 27 апреля 2009

Предвидение ошибок памяти

Я удивлен, что не упомянул об этом в других постах, но вы можете использовать ManagementFactory в Java 5/6, чтобы получить много информации об использовании памяти.

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

5 голосов
/ 27 апреля 2009

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

3 голосов
/ 27 апреля 2009

Большинство ошибок StackOverflow происходят из-за плохой рекурсии. К сожалению, проблема определения, остановится ли рекурсия, как правило, не решаема (это центральная концепция в CS). Однако в некоторых случаях вы можете получить предупреждения, например, некоторые IDE сообщат вам, если вы вызываете функцию рекурсивно без параметров.

2 голосов
/ 27 апреля 2009

Вы никогда не увидите исключение StackOverflow, если ваше приложение разработано и реализовано правильно!

Обычно, если вы получаете исключение StackOverflow, то это признак того, что в вашем коде рекурсии есть ошибка.

1 голос
/ 27 апреля 2009

MemoryMXBean может отправлять уведомления, если ваша память достигает определенного порога.

1 голос
/ 27 апреля 2009

Одна полезная вещь, которую вы можете сделать, это использовать SoftReference s для кэшей. Это даст вам постепенное снижение производительности, когда у вас не хватит памяти. Только не используйте WeakReference даже в WeakHashMap, потому что это будет раздражать меня, когда ваше приложение умирает от меня.

1 голос
/ 27 апреля 2009

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

Переполнения стека из-за бесконечной регрессии обычно появляются в цикле записи / отладки.

Сложнее обнаружить проблемы с памятью в таких вещах, как большие коллекции, кэши и т. Д. Как уже указывалось, вы можете проверить свободную и максимальную память во время выполнения, но будьте осторожны, так как их значения не очевидны - «свободный» здесь означает «свободный сейчас», например, Это означает, что если вы запустили полный сборник, вы можете получить больше. К сожалению, нет способа получить «все возможное бесплатно, включая сборщик мусора» без запуска System.gc (), что нехорошо делать в производственном приложении (где вы склонны иметь достаточно большие наборы данных для вызвать проблему в первую очередь), потому что вся JVM остановится на несколько секунд (или больше, в большом приложении). Обратите внимание, что даже System.gc () не гарантированно запускается «сейчас», но мой опыт таков, что он выполнялся всякий раз, когда я играл с ним.

Вы можете распечатать действие gc из запущенной jvm, запустив java с -verbose: gc, -XX: + PrintGCTimeStamps и -XX: + PrintGCDetails (более подробно здесь ), и в общем случае сборщик начинает работать чаще, это, вероятно, признак того, что вам не хватает памяти.

0 голосов
/ 28 мая 2019

Пример реализации с использованием MemoryPoolMXBean

import static java.lang.management.ManagementFactory.getMemoryMXBean;
import static java.lang.management.ManagementFactory.getMemoryPoolMXBeans;
import static java.lang.management.MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED;
import static java.lang.management.MemoryType.HEAP;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.util.Collection;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.management.Notification;
import javax.management.NotificationEmitter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MemoryWatcher {

    public interface Listener {
        void memoryUsageLow(long usedMemory, long maxMemory);
    }

    private static final MemoryPoolMXBean tenuredGenPool = findTenuredGenPool();
    private static final Logger logger = LoggerFactory.getLogger(MemoryWatcher.class);

    private static MemoryPoolMXBean findTenuredGenPool() {
        for (MemoryPoolMXBean pool : getMemoryPoolMXBeans())
            if (pool.getType() == HEAP && pool.isUsageThresholdSupported())
                return pool;
        return null;
    }

    public static MemoryPoolMXBean getTenuredGenPool() {
        return tenuredGenPool;
    }

    private final Collection<Listener> listeners = new CopyOnWriteArrayList<>();

    public MemoryWatcher(double usageThresholdPercent) {
        if (tenuredGenPool == null) {
            logger.warn("Tenured pool is not used");
            return;
        }
        if (tenuredGenPool.getUsageThreshold() != 0)
            logger.warn("Overriding tenured usage threshold {} with {}", tenuredGenPool.getUsage().getMax() / (double) tenuredGenPool.getUsageThreshold(), usageThresholdPercent);
        tenuredGenPool.setUsageThreshold((long) (tenuredGenPool.getUsage().getMax() * usageThresholdPercent));

        NotificationEmitter emitter = (NotificationEmitter) getMemoryMXBean();
        emitter.addNotificationListener((Notification n, Object hb) -> {
            MemoryUsage usage = tenuredGenPool.getUsage();
            if (n.getType().equals(MEMORY_THRESHOLD_EXCEEDED) && usage.getMax() == usage.getCommitted())
                listeners.forEach(listener -> listener.memoryUsageLow(usage.getUsed(), usage.getMax()));
        }, null, null);
    }

    public boolean addListener(Listener listener) {
        return listeners.add(listener);
    }

    public boolean removeListener(Listener listener) {
        return listeners.remove(listener);
    }
}
0 голосов
/ 27 апреля 2009

Я не знаю, как предотвратить эти ошибки, но если вы добавите необработанный обработчик исключений в поток, по крайней мере, вы сможете войти в систему и, возможно, выполнить какое-то восстановление в другом потоке.

0 голосов
/ 27 апреля 2009

Для ошибки StackOverflowError:

Чтобы узнать текущую глубину, обычно это либо:

  1. с использованием функции с сохранением состояния (сохранение глубины вне функции)
  2. с использованием аккумулятора (передача глубины в качестве аргумента функции)

Знать глубину, на которой это произойдет, сложно. Есть несколько факторов:

  1. Пространство стека, выделенное для JVM (вы можете изменить это с помощью опции -Xss)
  2. Количество уже использованного стекового пространства
  3. Количество, используемое текущей функцией.

Почему бы не попробовать что-нибудь подобное?

public static void main(String[] args) {
    try {
        recurs();
    } catch (Throwable t) {
        // not a good idea in production code....
    }
}
static int depth = 0;
static void recurs() {
    System.out.println(depth++);
    recurs();
}

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

Полагаю, что кроме переписывания алгоритма, единственным вариантом было бы увеличение пространства стека с помощью опции -Xss.

Для OutOfMemoryError есть опция -Xmx

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...