Подтвердить что ты не робот

Java vm замедляется со всеми потоками, занятыми строковыми операциями

Я сталкиваюсь с очень своеобразной проблемой. Мой tomcat отлично работает на 25% CPU 24/7, но через несколько дней мой процессор стреляет до 60%, и система останавливается и не восстанавливается.

Когда я беру дамп потока во время замедления, почти все потоки заняты какой-то строкой или связанной с ней операцией.

Нет ошибок OutOfMemory или любых исключений, все запросы все еще обрабатываются, но время отклика ухудшается до n-й степени, где даже второй секундный запрос замедляется до 60 секунд и более.

Конфигурация моего сервера следующая:

    Ubuntu 12.04.2 LTS
    Linux 3.2.0-38-virtual #60-Ubuntu SMP x86_64 x86_64 x86_64 GNU/Linux
    java version "1.7.0_13"
    Java(TM) SE Runtime Environment (build 1.7.0_13-b20)
    Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode)
    export JAVA_OPTS='-server
    -Xms18g -Xmx18g
    -XX:MaxPermSize=512m
    -XX:ThreadStackSize=512
    -XX:NewRatio=1
    -XX:SurvivorRatio=4
    -XX:+UseConcMarkSweepGC
    -XX:+UseParNewGC
    -XX:+CMSClassUnloadingEnabled
    -Xloggc:/usr/tomcat/logs/gc.log
    -XX:+PrintGCDetails
    -XX:+PrintGCDateStamps
    -XX:+PrintTenuringDistribution
    -Dcom.sun.management.jmxremote
    -Dcom.sun.management.jmxremote.port=9999
    -Dcom.sun.management.jmxremote.authenticate=false
    -Dcom.sun.management.jmxremote.ssl=false
    -Djava.awt.headless=true'

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

Нажмите здесь, чтобы загрузить журнал vmstat

Нажмите здесь, чтобы загрузить журнал gc

Любые идеи относительно причины этого? Благодаря

4b9b3361

Ответ 1

Попробуйте увеличить максимальный размер кеша кода с помощью следующей опции JVM:

-XX:ReservedCodeCacheSize=256m

См. мой ответ на другой вопрос для фона этого предложения.

Ответ 2

Чтобы попытаться выявить оскорбительные запросы, вы можете настроить Stuck Thread Detection Valve в Tomcat.

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

Когда такой запрос обнаружен, текущая трассировка стека его потока записывается в журнал Tomcat с уровнем WARN.

Идентификаторы и имена застрявших потоков доступны через JMX в атрибутах stuckThreadIds и stuckThreadNames. Идентификаторы могут использоваться со стандартным Threading JVM MBean (java.lang: type = Threading) для получения другой информации о каждой застрявшей нити.

Ответ 3

Если загрузка процессора ниже 100%, и все же приложение остановлено, это означает, что что-то не позволяет полностью использовать процессор.

I/O или чрезмерное переключение контекста (например, вызванное блокировками) являются обычными виновниками этого.

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

Ответ 4

Это не проблема памяти, так как на вашем дампе GC не занят и доступной памяти достаточно. Кроме того, CPU застрял на 60%, но если приложение будет занято вычислением (GC или что-то еще), он будет застрял бы на 100%, так же, если это была сетевая атака. Таким образом, источник этой проблемы должен включать в себя некоторую операцию с дисковым IO.

Известно, что Tomcat является ошибкой и имеет несколько серьезных проблем. Одна вещь, с которой я столкнулась, заключается в том, что без какой-либо конкретной причины Tomcat внезапно затопил свой собственный журнал с бессмысленными записями. Это не только заставило диск заполнить до 100%, но и значительно замедлило входящие запросы. Вы можете проверить это, взглянув на журналы tomcat и его размер.

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

Ответ 5

Я думаю, что ваша проблема заключается в этом решении конфигурации -XX:PermSize=320m -XX:MaxPermSize=320m, не позволяя вашему PemSpace динамически меняться, вы вызываете тупик, когда вы его исчерпаете, - помните, что межсетевой кеш использует PermSpace. Я попробую изменить -XX:MaxPermSize=320m на -XX:MaxPermSize=512m.

Ответ 6

Существуют ли какие-либо аномалии в GC log? Кажется, что вы работаете с довольно большой кучей с некоторыми необычными опциями и делаете много строк, выделяющих материал. Возможно, вы страдаете от фрагментации кучи с течением времени (CMS не компактно). Также убедитесь, что никакая перестановка не происходит (может случиться, если куча слишком велика, поэтому ее редко посещает VM)

Я бы заподозрил, что это связано с GC, поскольку, по-видимому, поток не заблокирован. Вы пробовали с более недавним JDK? Также вы можете повторить попытку, но удалить несколько необычный вариант -XX: + CMSScavengeBeforeRemark, потому что у них может быть не так много тестового покрытия с каждой младшей версией JDK.

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

Ответ 7

Вам нужно использовать BTrace диагностику вызова метода.

Напишите запись script следующим образом:

Trace com.xx.xx класс префикса, которые вызывают String любым методом и время печати вызывают.

@TLS
private static Map<String, Integer> countMap = BTraceUtils.newHashMap();

private static String prefix = "com.xx.xx";// package like com.xx.xx which you want to trace ()

@OnMethod(clazz = "java.lang.String", method = "/.*/") //all method in String
public static void traceMethodInvoke() {
    String str = BTraceUtils.jstackStr();
    for (String currentClass : str.split("\\n")) {
        if (BTraceUtils.Strings.startsWith(currentClass, prefix)) {
            if (!countMap.containsKey(currentClass)) {
                countMap.put(currentClass, 1);
            } else {
                countMap.put(currentClass, countMap.get(currentClass) + 1);
            }
            break;
        }
    }
}

@OnTimer(5000)
public static void print() {
    BTraceUtils.println("========================================");
    for (Map.Entry<String, Integer> entry : countMap.entrySet()) {
        if (entry.getValue() > 100) {// print if cont > 10
            BTraceUtils.println(entry.getValue() + "\t\t" + entry.getKey());
        }
    }
    BTraceUtils.println("===========================================");

}  

Результат выводится следующим образом:

====================================================
1022                           com.xx.xx.classA#m1
322                            com.xx.xx.classA#m2
2022                           com.xx.xx.classA#m21
422                            com.xx.xx.ccc.classX#m11
522                            com.xx.xx.zz.classS#m44
.........

Вы можете изменить prefix для отслеживания другого префикса пакета.

В результате вы можете проанализировать исходный код и выяснить проблемы.

Ответ 8

Просканировав поток нитей, смотрящий на потоки RUNNABLE, выделяется одна вещь. Кажется, что ваша система обрабатывает или пытается обрабатывать большое количество запросов одновременно. И если у вас есть несколько ядер, вероятно, будет много времени нарезки. С другой стороны, я не вижу ясных → доказательств < что это связано с GC. (Но вы не включили журналы GC...)

Я предлагаю вам взглянуть на две вещи.

  • Посмотрите статистику виртуальной памяти операционной системы. Одной из возможных причин катастрофического замедления системы является избиение виртуальной памяти. Здесь общая потребность приложений для страниц виртуальной памяти превышает доступную физическую память... и операционная система тратит много времени на обмен файлами между физической памятью и своп-диском/файлом страницы.
  • Посмотрите на образец запросов, которые вы получаете. Возможно, в определенное время количество/тип запросов, которые вы получаете, просто превышает емкость вашей системы.

Если проблема связана с VM, то решение заключается в сокращении потребности в памяти приложения. Простой способ сделать это: уменьшить размер кучи Java.

Если проблема связана с нагрузкой, решить ее труднее:

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

Наконец, вы можете увидеть, помогает ли ему переключиться с CMS на Parallel Collector; обратитесь к странице настройки GC GC GC: Доступные коллекторы. Но я сомневаюсь, что это проблема GC.

Ответ 9

Первое, что вы должны предпринять, - это выяснить, какие потоки фактически потребляют процессор. Это могут быть потоки, которые при выполнении строковых операций, или это может быть другой поток VM, который может выполнять операции GC и Sweep. Ссылка говорит о том, как соотносить всплески процессора с дампом потока

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

Надеюсь, что это поможет