Pull to refresh

Comments 31

Как бы банально не звучало, но — Спасибо за статью! У нас как раз в пятницу WebLogic падал с OOM. Так что статья вовремя — завтра будем её использовать. Кстати, по повду jmap, для дампа целевое приложение не нужно запускать с какими то параметрами? Т.е. вот работает приложение в проде без опций heap dump и можно ли с помощью jmap сделать дамп памяти это приложения?
Пожалуйста, я буду рад, если поможет.
Если я правильно разобрался в проблемной области, то jmap требуется запускать от того же пользователя, под которым запущено приложение. Отсюда танцы с бубном вокруг PsExec под виндой, ведь томкет в ней запускается от системы в случае, если устанавливался как служба.
Да, jmap не связан никак с опцей автоматической генерации дампа.
Самое коварное — это когда память течет и заканчивается потихоньку. В этом случае потоки еще не падают с ООМ, но все большую часть процессорного времени начинает отжирать GC. В результате сервер «встает колом» — ошибок нет, но все повисает, пользователи отваливаются, система фактически перестает реагировать на внешние раздражители. Дамп памяти «наживую» создать при этом невозможно, штатно завершить работу сервера — тоже. То есть если не мониторить heap, то выглядит это как повисание сервера намертво через несколько дней работы…
Если память течёт совсем медленно, мы обычно создаём на живую два дампа с помощью jmap с некоторым интервалом времени, потом внимательно смотрим разницу.
Отвечаю, что использовать не пробовали, но спасибо, что подсказали, когда появится время — надо бы посмотреть, что он из себя представляет.
Скажу лишь, что под капотом DBCP 2.0 уже располагается commons-pool2, это было причиной переименования параметров.
В pom'нике в ветке trunk проекта commons-dbcp2 сейчас наблюдается строчка
<commons.pool.version>2.3</commons.pool.version>
Если прогуляться в класс http://svn.apache.org/viewvc/commons/proper/pool/tags/POOL_2_3/src/main/java/org/apache/commons/pool2/impl/GenericObjectPool.java?view=markup, то в строке 1132 можно увидеть строчку
private final LinkedBlockingDeque<PooledObject<T>> idleObjects;
Ну и в методе «public T borrowObject(long borrowMaxWaitMillis)» (строка 411) эта очередь активно используется.
Думаю, что DBCP 2 должен быть быстрее, чем 1.4, и у меня даже есть желание перейти на DBCP 2+.
Надо бы посмотреть содержимое и попробовать провести бенчмарк, который предоставляет разработчик HikariCP, чтобы понять, каков этот коэффициент ускорения.
По предоставленной Вами ссылке так и написано в первых двух пунктах, что мол «Note that this does not apply to Commons DBCP 2.x.».
Есть один неочевидный и коварный аспект Out of Memory ошибок, который может натворить много бед в продакшене и о котором стоит сказать. Суть в том, что в зависимости от реализации приложение в полумертвом OOM-состоянии может нормально отвечать на запросы внешнего мониторинга (например, отдавать страницу 200 ОК или возвращать утвердительный ответ о работоспособности иным способом, например по JMS), при этом де-факто уже не являясь работоспособным.

Поэтому если речь идет о mission-critical задачах, может быть уместно устанавливать дефолтный эксепшн хендлер (см. Thread.setDefaultUncaughtExceptionHandler()) и вызывать System.exit() как только прилетает упоминание OutOfMemoryError.
А чем поможет System.exit в таком случае? С полуживого приложения проще собрать диагностику и понять, что случилось, чем с завершившегося.
Тем, что мониторинг обнаружит неработоспособность приложения сразу, а не через несколько часов например.
Более правильным вариантом будет всё же натравить тот же Zabbix, например, на слежение за логфайлом, в который пишется OOM, чтобы он сигнализировал как-то об этом событии, и админы уже могли и дампы снять, и перевалить приложение. Плюс никто не запрещает автоматизировать процесс, мне кажется.
Вдобавок можно также следить за какими-то критическими вещами, типа слова Abandoned в том-же логе.
Как именно обрабатывать событие в обработчике Thread.UncaughtExceptionHandler – вопрос второй, можно конечно и в лог писать а потом эти логи парсить. Если конечно есть уверенность в том, что OOM точно не поломает логгинг.

Речь здесь скорее про то, что а) для ловли/логгирования таких ошибок удобно использовать Thread.UncaughtExceptionHandler б) при планировании мониторинга нужно предусматривать кейсы, когда приложение полуживое, но находится в состоянии Out of Memory.
Не подскажете, как использовать приведенные в статье инструменты для анализа OOM в GUI приложении под Windows?
К примеру, я попытался снять дамп памяти, установил -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=.
Поймал OOM, но дампа не появилось.
Сначала отвечу на Ваш вопрос:
Начну, пожалуй, с примеров кода...
Тестовый класс
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class TestOOM {
    public static void main(String[] args) throws IOException {
        BufferedReader rdr = new BufferedReader(new InputStreamReader(System.in, "UTF-8"));
        System.out.println("Press Enter to make OOM...");
        rdr.readLine();
        StringBuilder stringBuilder = new StringBuilder();
        while (true) {
            stringBuilder.append("foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar");
        }
    }
}
Запускаем это дело
$ ls
TestOOM.class

$ java -Xmx256m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=. TestOOM
Press Enter to make OOM…

java.lang.OutOfMemoryError: Java heap space
Dumping heap to ./java_pid15967.hprof…
Heap dump file created [85330696 bytes in 0,401 secs]
Exception in thread «main» java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2367)
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:130)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:114)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:415)
at java.lang.StringBuilder.append(StringBuilder.java:132)
at TestOOM.main(TestOOM.java:16)
В примере выше я указал -Xmx256m, чтобы оно не генерировало дамп памяти на 5 гиг.
В результате в папке, в которой я запускал «приложение», рядом с класс-файлом появился файл java_pid15967.hprof.
В качестве рекомендаций могу посоветовать проверить, что перед параметром HeapDumpOnOutOfMemoryError стоит знак плюс, "-XX:+HeapDumpOnOutOfMemoryError", если будет минус ("-XX:-HeapDumpOnOutOfMemoryError"), то это наоборот, «выключить» (надо, кстати, об этом ещё кое-куда написать, а то чувствую, насоветовал я там человеку...).
К тому же, если есть возможность «подключиться» к процессу, который запущен, с помощью JVVM, то там будет видно, включена ли опция, на вкладке «Overview» будет чёрным по серому написано «Heap dump on OOME: enabled».
Вот, как-то так обстоят дела.
Спасибо за ответ.
Ключи я указал правильно. Дело еще в том, что приложение не мое и исходников у меня сейчас нет. Возможно там OOME перехватыватеся и как-то обрабатывается. Это может повлиять на создание дампа? Правда в консоли и в логе стектрейс с OOME появляется.
Да, его можно перехватить, выдать в лог, и не пропустить дальше. В консоль и лог OOM в этом случае попадёт, но JVM о нём вроде как не узнает.
Почему перехват Throwable нужно делать с полным знанием дела
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.logging.Level;
import java.util.logging.Logger;

public class TestOOM {
    private static final Logger LOGGER = Logger.getLogger(TestOOM.class.getName());

    public static void main(String[] args) throws IOException {
        BufferedReader rdr = new BufferedReader(new InputStreamReader(System.in, "UTF-8"));
        LOGGER.info("Press Enter to make OOM...");
        rdr.readLine();
        StringBuilder stringBuilder = new StringBuilder();
        try {
            while (true) {
                stringBuilder.append("foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar");
            }
        } catch (Throwable thr) {
            LOGGER.log(Level.SEVERE, "Here is a some Throwable, printing it and ignoring.", thr);
        }
        LOGGER.info("I am still alive! Press Enter to quit...");
        rdr.readLine();
    }
}
Хотя нет, я соврал, я пропустил заветную строчку «Dumping heap to ./java_pid6902.hprof ...». Однако приложение всё равно «выживает». Возможно, такие конструкции с перехватом Throwable как раз и являются причиной варианта «приложение всё ещё живое, но не отвечает».
Заголовок спойлера
$ java -Xmx256m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=. TestOOM
мар 23, 2015 5:26:35 PM TestOOM main
INFO: Press Enter to make OOM...

java.lang.OutOfMemoryError: Java heap space
Dumping heap to ./java_pid6902.hprof ...
Heap dump file created [85808247 bytes in 0,406 secs]
мар 23, 2015 5:26:36 PM TestOOM main
SEVERE: Here is an some Throwable, printing it and ignoring.
java.lang.OutOfMemoryError: Java heap space
        at java.util.Arrays.copyOf(Arrays.java:2367)
        at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:130)
        at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:114)
        at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:415)
        at java.lang.StringBuilder.append(StringBuilder.java:132)
        at TestOOM.main(TestOOM.java:17)

мар 23, 2015 5:26:36 PM TestOOM main
INFO: I am still alive! Press Enter to quit...
В MAT OQL тоже можно пользоваться. Он немного мутный, но работает. А ещё там есть полезные вещи типа Merge shortest paths to GC roots. Ну и подсчёт retained size. Довольно магическая штука, не всегда работает как ожидается, но порой выручает.

В одном из приложений мы в обёртку к SQL-соединениям добавили текстовое поле с текстом последнего запроса, выполнявшегося в этом соединении. Исключительно для того, чтобы в дампе памяти его можно было легко найти.
Спасибо автору за стать
но вот что меня заинтересовало:

at наш.пакет.СуперКласс.getConnection(СуперКласс.java:100500)
at наш.пакет.СуперКласс.плохойПлохойМетод(СуперКласс.java:100800)


это реальные цифры? это сгенерированный код? мне очень трудно представить как иначе класс мог бы разрастись до столь не вменяемых размеров
Нет, класс вполне обычный, я просто скрыл содержимое стектрейса, а в качестве номеров строк указал сие магическое число, это просто отсылка к мему "Стопицот".
Я даже сомневаюсь, что во всём проекте столько строк наберётся.
Еще стоит посмотреть доклад Володи Ситникова — Анализ дампов памяти Java приложений


Там и про jvisualvm, и про MAT, и про OQL и прикрученный Володей SQL движок к MAT'у, так, что можно писать куда более интересные запросы.
Отлично, как раз то, что хотелось узнать про MAT. Благодарю!
UFO just landed and posted this here
Эти проценты от общего числа упавших приложений или от общего числа протестированных? А тесты на какой версии Java проводились? Теоретически с седьмой количество PermGen space ошибко должно уменьшиться (а в восьмой и вовсе исчезнуть).

Иногда разумно продолжить работу приложения с помощью логирования catch Throwable. Есть шансы, что именно в этом try-блоке было выделено очень много памяти и при проваливании в catch она освободится.

Я стараюсь писать catch Throwable где-нибудь на самом верху — общий try-catch для фоновых задач, например. Тогда при OOM умрёт поток с фоновой задачей, но есть шансы, что выживет приложение. Обычно catch Throwable направлен на ловлю других ошибок — например, корявый деплой привёл к несоответствию версий библиотек и выпало что-нибудь вроде NoSuchMethodError. В современном мире такая ошибка не хуже NullPointerException. Ну то есть это откровенный баг и скорее всего какая-то подсистема полностью не функционирует, но не повод из-за него класть весь сервер. Или StackOverflowError — его тоже можно пережить. Возможно, стоит подходить более дифференцировано к Error-исключениям и обрабатывать OOM по особенному. Слать СМС-ку админу, например :-)
UFO just landed and posted this here
// Хозяйке на заметку
С помощью ключа -XX:OnOutOfMemoryError=«kill -9 %p» можно повесить на OutOfMemory ошибку определенные действия, например jstack, jsmap и kill, упакованные в shell-скрипт.

Кавычки конечно должны быть не елочки, а самые обыкновенные :)
Тот же apache solr с 5 версии так и делает. OOM там может и не повлиять на доступность на чтение, в отличии от доступности на запись.
UPD 3: markt в мейлинг-листе томкета отвечал на вопрос, какого чёрта в томкете два коннекшн-пула одновременно (tomcat-jdbc и одновременно DBCP, и почему внезапно в восьмом томкете DBCP стал выгоднее): http://mail-archives.apache.org/mod_mbox/tomcat-users/201506.mbox/%3C556EC74A.5040306%40apache.org%3E.
Sign up to leave a comment.

Articles

Change theme settings