понедельник, 21 июля 2014 г.

Какой Java поток нагружает мой процессор

Какой Java поток нагружает мой процессор из песочницы tutorial

Что Вы делаете, когда Ваше Java приложение потребляет 100% ЦП? Оказывается Вы легко можете найти проблемные потоки, используя встроенные Unix и JDK утилиты. Никакие инструменты профилирования не потребуются.
С целью тестирования мы будем использовать простую программу:

public class Main {
    public static void main(String[] args) {
        new Thread(new Idle(), "Idle").start();
        new Thread(new Busy(), "Busy").start();
    }
}
 
class Idle implements Runnable {
 
    @Override
    public void run() {
        try {
            TimeUnit.HOURS.sleep(1);
        } catch (InterruptedException e) {
        }
    }
}
 
class Busy implements Runnable {
    @Override
    public void run() {
        while(true) {
            "Foo".matches("F.*");
        }
    }
}

Как вы видите, в данном куске кода запускается 2 потока. Idle не потребляет ресурсы ЦП(
запомните, спящие потоки потребляют память, но не процессор), в то время как Busy сильно нагружает ЦП, выполняя парсинг регулярных выражений, и другие сложные процессы.
Как мы можем быстро найти проблемный кусок кода нашей программы? Во-первых мы будем использовать 'top', чтобы найти Id процесса(PID) java приложения. Это весьма просто:
top -n1 | grep -m1 java

Мы увидим первую строку вывода 'top', содержащую слово «java»:
22614 tomek     20   0 1360m 734m  31m S    6 24.3   7:36.59 java 

Первая колонка это PID. К сожалению, оказалось, что 'top' использует ANSI escape codes for colors. К счастью, я нашел perl скрипт, чтобы удалить лишние символы и наконец-то извлечь PID.
top -n1 | grep -m1 java | perl -pe 's/\e\[?.*?[\@-~] ?//g' | cut -f1 -d' '

Возвращает:
22614

Теперь, когда мы знаем PID процесса, мы можем использовать top -H, для поиска проблемных Linux потоков. Ключ -H отображает список всех потоков, и теперь колонка PID это ID потока:
top -n1 -H | grep -m1 java
top -n1 -H | grep -m1 java | perl -pe 's/\e\[?.*?[\@-~] ?//g' | cut -f1 -d' '

Возвращает:
25938 tomek     20   0 1360m 748m  31m S    2 24.8   0:15.15 java
25938

Итого мы имеем ID процесса JVM и ID потока Linux. А теперь самое интересное: если вы посмотрите на вывод jstack (доступен в JDK), каждый поток имеет NID, который написан после имени.
Busy' prio=10 tid=0x7f3bf800 nid=0x6552 runnable [0x7f25c000]
    java.lang.Thread.State: RUNNABLE
        at java.util.regex.Pattern$Node.study(Pattern.java:3010)

Параметр nid=0x6552 это Hex представление ID потока:
printf '%x' 25938
6552

Теперь объединим всё в один скрипт:
#!/bin/bash
PID=$(top -n1 | grep -m1 java | perl -pe 's/\e\[?.*?[\@-~] ?//g' | cut -f1 -d' ')
NID=$(printf '%x' $(top -n1 -H | grep -m1 java | perl -pe 's/\e\[?.*?[\@-~] ?//g' | cut -f1 -d' '))
jstack $PID | grep -A500 $NID | grep -m1 '^$' -B 500

Последняя строка запускает jstack с определённым PID и выводит поток с совпадающим NID. Тот самый поток и будет являться проблемным.
Выполняем:
./profile.sh
"Busy" prio=10 tid=0x7f3bf800 nid=0x6552 runnable [0x7f25c000]
    java.lang.Thread.State: RUNNABLE
        at java.util.regex.Pattern$Node.study(Pattern.java:3010)
        at java.util.regex.Pattern$Curly.study(Pattern.java:3854)
        at java.util.regex.Pattern$CharProperty.study(Pattern.java:3355)
        at java.util.regex.Pattern$Start.<init>(Pattern.java:3044)
        at java.util.regex.Pattern.compile(Pattern.java:1480)
        at java.util.regex.Pattern.<init>(Pattern.java:1133)
        at java.util.regex.Pattern.compile(Pattern.java:823)
        at java.util.regex.Pattern.matches(Pattern.java:928)
        at java.lang.String.matches(String.java:2090)
        at com.blogspot.nurkiewicz.Busy.run(Main.java:27)
        at java.lang.Thread.run(Thread.java:662)


Источник
+26
12654
243
syntax 2,0

Комментарии (13)

+1
mhspace,#
top -n1 | grep -m1 java

У sysvinit есть утилита pidof. Для systemd есть systemd-sysvcompat, в котором есть эта утилита.
0
sergeisirik,#
А под винду не подскажете подход?
0
syntax,#
Посмотрите тут
0
sergeisirik,#
Спасибо!
0
qasta,#
Полезная заметка — добавил в закладки. Один раз пришлось все те же самые действия делать руками (htop + jvisualvm). Из скрипта делать это намного проще.
Единственное, -A500, наверное перебор (это же 500 строк после matching line, если я правильно помню?).
+10
alexeygrigorev,#
awk '{print $1}' вместо perl -pe 's/\e\[?.*?[\@-~] ?//g' возможно, будет лучше
+1
dokwork,#
Спасибо за перевод. Полезно, интересно. Лучи добра оригинальному автору.
+1
Zay4egg,#
Потребляет — значит работает.
+1
harlov91,#
Если даже jstack висит — наверное все плохо, да?
0
kos32,#
Я пользовался плагином top-threads для jconsole
Мне помог.
+1
kormushin,#
Достаточно использовать
top -b -n1
чтобы избавиться от escape последовательностей.

Спасибо! Про -H и native id полезная штука.
+1
nfubh,#
А что если будет несколько загруженных процессов/потоков? Скрипт это никак не учитывает. Вот вам более универсальный вариант:

$ cat test.sh
#!/bin/bash
PIDS=`ps -o comm,%cpu,pid | awk 'NR > 1 && $1 == "java" && $2 > 90 {print $3;}'`
[ -n "$PIDS" ] || exit;
echo "Busy Java processes:"
for PID in $PIDS; do
    NIDS=`ps -mp $PID -o %cpu,lwp | awk 'NR > 2 && $1 > 90 {print $2;}'`
    [ -n "$NIDS" ] || continue
    echo "$PID, busy threads: $NIDS"
done
$ java Main &
[1] 26908
$ java Main &
[2] 26929
$ ./test.sh
Busy Java processes:
26908, busy threads: 26927
26929, busy threads: 26948
0
SAN_Antonio,#

Комментариев нет:

Отправить комментарий