Development

Правка инструмента

Изменение OpenJDK 8 для отображения имени потока в Linux.

December 21 2015

Когда ваш Java сломан

Большая часть платформы для обработки сообщений Infobip и соответствующие веб-сервисы написаны на Java. Распространенность технологии обеспечивает стабильность и легкость разработки, большое количество поддерживающих библиотек, решений для устранения багов и профилирования, иногда все же встречаются раздражающие дефекты, в существование которые сложно поверить. А они есть на самом деле. На Linux, например, вы не увидите имя потока Java, если рассмотрите его собственными процессы. Поскольку потоки Java соотносятся один к одному с потоками собственной системы, было бы удобно иметь возможность увидеть только «верхушку» и посмотреть, какой же поток больше всего использует процессор в данный момент, без применения инструментов по устранению ошибок. Еще больше раздражает, что раньше все работало на Oracle JRockit 6 и каким-то образом затерялось при переходе на OpenJDK 7 и 8. А самое мучительное для гордых пользователей Linux – все это работает на Windows!

Итак, что можно предложить обычному Java разработчику? Вы можете потерять веру в успех при работе с такими мастодонтами, как Oracle с их программным обеспечением, опустить руки и смириться с этим. Или же вы можете принять вызов, поработать над своими навыками и не отступать пока решение проблемы не будет найдено. Мы в Infobip относимся именно ко второй категории.

Создайте свой Java

Небольшое исследование показало, что существует расширение GNU к POSIX , определяющий API и позволяющий присваивать имя потока в Linux, называется оно pthread_setname_np. Согласно документации, Linux сохраняет имена процессов (описания или имена команд, если вам удобнее) в /proc/[pid]/comm, и ограничивает их 15ю символами. Для таких инструментов Unix, как ps и top это может оказаться полезным. Давайте посмотрим, как это работает:


$ ps | grep $$ # $$ is current PID
7880 pts/3 00:00:00 bash
$ echo -n "Hi mum, look at me!" > /proc/$$/comm
$ ps | grep $$
7880 pts/3 00:00:00 Hi mum, look at me!

В top вы можете нажать клавишу 'c' для переключения между командной строкой (cmd line) и отображением имени команды

Теперь давайте сделаем так, что Java будет использовать этот API для присвоения нативного имени потока. Легко сказать, правда? OpenJDK – это открытый источник данных, поэтому давайте сперва загрузим исходники:

$ hg clone http://hg.openjdk.java.net/jdk8u/jdk8u60
$ cd jdk8u60
$ sh get_source.sh

Давайте попробуем покопаться там, где заканчивается java.lang.Thread.setName(String arg0). Для этого вам понадобится давно забытый навык чтения C++, но найти эту часть кода не так сложно:

hotspot/src/os/linux/vm/os_linux.cpp
void os::set_native_thread_name(const char *name) {
// Not yet implemented.
return;
}
 
bool os::distribute_processes(uint length, uint* distribution) {
// Not yet implemented.
return false;
}
 
bool os::bind_to_processor(uint processor_id) {
// Not yet implemented.
return false;
}

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

Если вам некомфортно изменять код JVM, вы не одиноки – но кто-то уже сделал это до вас. Отдельная темапо этому улучшению вместе с работающим решением была открыта еще в 2011м году и переросла в абстрактные рассуждения, которые привели к созданию запроса к glibc на снятие ограничения в 15 символов. Но этого не произойдет, поскольку ограничение жестко закодировано глубоко в ядре Linux. Кажется, что улучшение наконец-то было учтено в Java 9, но для тех, кто не хочет ждать мы предлагаем использовать следующий патч.

Создание патча
jdku60$ patch -d hotspot -p1 <../patches/thread_name.cpp.patch

Работа с OpenJDK на удивление легка. Java 9 переключилась на cmake , но с Java 8 вам придется работать с автоинструментами:

$ sh conigure.sh 
# <do whatever the script tells you to do>
$ make

Билд создал файл build/linux-x86_64-normal-server-release/hotspot/dist/jre/lib/amd64/server/libjvm.so, который можно использовать для замены такого же файла в стандартном дистрибутиве JVM. А теперь попробуйте магию:

ThreadNameTest.java
public class ThreadNameTest {
    public static void main(String[] args) throws Exception {
        Thread.currentThread().setName("Hi mum, look at me!");
        Thread.sleep(100_000);
    }
}
Java с поддержкой имени
$ javac ThreadNameTest.java
$ java ThreadNameTest &
[1] 19391
$ for PID in `ls /proc/$!/task/`; do echo $PID - `cat /proc/$!/task/$PID/comm`; done
19563 - java
19564 - Hi mum, look at
19565 - java
19566 - java
19567 - java
19568 - java
19569 - java
19570 - java
19571 - java
19572 - java
19573 - java
19574 - Reference Handl
19575 - Finalizer
19576 - Signal Dispatch
19577 - C2 CompilerThre
19578 - C2 CompilerThre
19579 - C2 CompilerThre
19580 - C1 CompilerThre
19581 - Service Thread
19582 - java
$ kill -s 15 $!
[1]+  Exit 143                java ThreadNameTest
Vanilla Java
$ javac ThreadNameTest.java
$ java ThreadNameTest &
[1] 19391
$ for PID in `ls /proc/$!/task/`; do echo $PID - `cat /proc/$!/task/$PID/comm`; done
19563 - java
19564 - java
19565 - java
19566 - java
19567 - java
19568 - java
19569 - java
19570 - java
19571 - java
19572 - java
19573 - java
19574 - java
19575 - java
19576 - java
19577 - java
19578 - java
19579 - java
19580 - java
19581 - java
19582 - java
$ kill -s 15 $!
[1]+  Exit 143                java ThreadNameTest

(Автор Milan Mimica, Разработчик ПО/Руководитель рабочей группы)