Как узнать pid программы java

Обновлено: 04.07.2024

В программе курса Разработчик Java довольно много тем, посвященных внутренностям работы JVM. Мы разбираемся в механизмах работы коллекций, байт-кода, сборщика мусора и т.д. Сегодня предлагаем Вашему внимаю перевод довольно интересной статьи о thread dump-е. Что это такое, как его получить и как использовать.

Хотите узнать, как анализировать thread dump (дамп потоков)? Заходите под кат, чтобы узнать больше о том как в Java получить thread dump и что с ним потом делать.

Большинство современных Java-приложений являются многопоточными. Многопоточность может существенно расширить функционал приложения, в то же время она вносит существенную сложность.

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

В случае многопоточных приложений необходимо найти компромисс между усложнением программы и возможным повышением производительности, когда несколько потоков могут использовать все доступные (часто больше одного) ядра центрального процессора (CPU). Если сделать все правильно, то используя многопоточность (формализована в Amdahl's Law), можно добиться существенного прироста производительности приложения. Однако при этом надо помнить об обеспечении синхронного доступа нескольких потоков к разделяемому ресурсу. В большинстве случаев, фреймворки, такие как Spring, инкапсулируют работу с потоками и скрывают от пользователей многие технические детали. Однако и в случае применения современных сложных фреймворков что-то может пойти не так, и мы, как пользователи, столкнемся со сложно решаемыми багами многопоточности.

К счастью, Java оснащена специальным механизмом для получения информации о текущем состоянии всех потоков в любой момент времени — это thread dump (своего рода моментальный снимок). В этой статье мы изучим, как получить thread dump для приложения реалистичных размеров и как этот dump проанализировать.

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

Основная терминология

С первого взгляда Java thread dump-ы могут показаться «китайской грамотой», ключом к ее понимаю являются следующие понятия. В общем, давайте, повторим основные термины многопоточности, которые будем использовать для анализа дампов.

    Thread или поток — дискретная единица многопоточности, управляемая Java Virtual Machine (JVM). Потоки JVM соответствуют потокам в операционной системе (OS) — native threads («естественные потоки»), которые и реализуют механизм выполнения кода.

У каждого потока есть уникальный идентификатор и имя. Потоки могут быть «демонами» и «не демонами».

Программа завершает свою работу, когда завершаются все потоки «не демоны» или вызывается метод Runtime.exit. Работающие «демоны» не влияют на завершение работы программы. Т.е. JVM ждем когда доработают все «не демоны» и завершает работу, на «не демонов» не обращает внимание.

За более подробной информацией обращайтесь к документации класса Thread.
Поток может находится в одном из следующих состояний:

  • Alive thread или «живой» — поток, который выполняет некоторую работу (нормальное состояние).
  • Blocked thread или «заблокированный» — поток, который попытался зайти в секцию синхронизации (synchronized), однако другой поток уже успел зайти в этот блок первым, и все следующие потоки, которые попытаются зайти в этот же блок оказываются заблокированными.
  • Waiting thread или «ожидающий» — поток, который вызвал метод wait (возможно, с указанием таймаута) и сейчас ждет, когда другой метод выполнит notify или nonifyAll на этом же объекте.

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

Более детальную информацию можно найти в этих источниках:

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

Создание примера программы

Прежде, чем создать thread dump, нам надо разработать Java-приложение. Традиционный «hello, world!» для нашей цели слишком прост, а дамп среднего размера приложения может оказаться слишком сложным для демонстрации. Исходя из этого, мы создадим достаточно простое приложение, в котором создаются два потока. Причем потоки попадают в deadlock:


Эта программа создает два ресурса: resourceA и resourceB, и стартует два потока: threadLockingResourceAFirst и threadLockingResourceBFirst, которые блокируют ресурсы друг друга.

Причиной возникновения deadlock-а является «перекрестная» блокировка ресурсов потоками.

Причиной возникновения deadlock является попытка «взаимного» захвата ресурсов, т.е. поток threadLockingResourceAFirst захватывает ресурс resourceA, поток threadLockingResourceBFirst захватывает ресурс resourceB. После этого поток threadLockingResourceAFirst, не отпуская свой ресурс, пытается захватить resourceB, а поток threadLockingResourceBFirst, не отпуская свой ресурс, пытается захватить ресурс resourceA. В результате потоки блокируются. Задержка в 1с добавлена, чтобы гарантировать возникновение блокировки. Потоки ждут освобождение нужных ресурсов, но это никогда не случится.

Вывод программы будет таким (числа после java.lang.Object@ будут разные для каждого запуска):

Генерация Thread Dump

На практике, Java-программа может аварийно завершиться и при этом создать thread dump. Однако в ряде случаев (например в случае deadlock-ов), программа не завершается и thread dump не создает, она просто зависает. Для создания дампа таких зависших программ, прежде всего надо выяснить идентификатор процесса программы, т.е. Process ID (PID). Для этого можно воспользоваться утилитой JVM Process Status (JPS), которая начиная с версии 7, входит в состав Java Development Kit (JDK). Чтобы найти PID процесса нашей зависшей программы, мы просто выполним jps в терминале (Windows или Linux):


Первая колонка — это идентификатор локальной виртуальной машины (Local VM ID, т.е. lvmid) для выполняемого Java-процесса. В контексте локальной JVM, lvmid указывает на PID Java-процесса.

Надо отметить, что это значение, скорее всего, будет отличаться от значения выше. Вторая колонка — это имя приложения, которое может указывать на имя main-класса, jar-файла или быть равно «Unknown». Все зависит от того, как приложение было запущено.

В нашем случае имя приложения DeadlockProgram — это имя main-классы, который был запущен при старте программы. В примере выше PID программы 11568, этой информации достаточно для генерации thread dump'а. Для генерации дампа мы воспользуемся утилитой jstack, которая входит в состав JDK, начиная с версии 7. Чтобы получить дамп мы передадим в jstack в качестве параметра PID нашей программы и укажем флаг -l (создание длинного листинга). Вывод утилиты перенаправим в текстовый файл, т.е. thread_dump.txt:


Полученный файл thread_dump.txt содержит thread dump нашей зависшей программы и содержит важную информацию для диагностики причин возникновения deadlock-а.

Если используется JDK до 7 версии, то для генерации дампа можно воспользоваться утилитой Linux — kill с флагом -3. Вызов kill -3 отправит программе сигнал SIGQUIT.

В нашем случае вызов будет такой:

Анализ простого Thread Dump

Открыв файл thread_dump.txt, мы увидим примерно следующее содержание:

Introductory Information

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

В первой строке указывается время, когда дамп был сформирован, во второй — диагностическая информация о JVM, на которой дамп был получен:


В этой секции нет информации о потоках. Тут задается общий контекст системы, в которой был собран дамп.

Общие сведенья о потоках

В следующей секции представлена информация о потоках, которые выполнялись в системе в момент сбора дампа:


В следующей секции приводится список:

В нем содержится информация о потоках за пределами JVM, т.е. это не потоки виртуальной машины и не потоки сборщика мусора. Если посмотреть на адреса этих потоков, то можно заметить, что они соответствуют значению tid — «естественному, железному» (native) адресу в операционной системе, а не Thread ID.

Троеточия используются для сокрытия излишней информации:

Потоки

Сразу после блока SMR следует список потоков. Первый поток в нашем списке — Reference Handler:

Краткое описание потока

Состояние потока

Вторая строка — это текущее состояние потока. Возможные состояния потока приведены в enum:
Thread.State:

NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED

Более подробную информацию смотрите в документации.

Thread Stack Trace

Следующая секция содержит stack trace потока в момент снятия дампа. Этот stack trace очень похож на stack trace, который формируется не перехваченным исключением. И содержит имена классов и строк, которые выполнялись в момент формирования дампа. В случае потока Reference Handler мы не видим ничего интересного.

Однако в трассировке потока Thread-02 есть кое-что интересное, отличное от стандартного трейса:


В трассировке мы видим, что добавилась информация о блокировке. Этот поток ожидает блокировку на объекте с адресом 0x00000000894465b0 (тип объекта java.lang.Object). Более того поток сам удерживает блокировку с адресом 0x00000000894465a0 (тоже объект java.lang.Object). Эта информация нам пригодится далее для диагностики deadlock-а.

Захваченные примитивы синхронизации (Ownable Synchronizer)

В последней секции приводится список примитивов синхронизации (synchronizers), захваченных потоком. Это объекты, которые могут быть использованы для синхронизации потоков, например, защелки (locks).

В соответствии с официальной документацией Java, Ownable Synchronizer — это наследники AbstractOwnableSynchronizer (или его подкласса), которые могут быть эксклюзивно захвачены потоком для целей синхронизации.

ReentrantLock и write-lock, но не read-lock класса ReentrantReadWriteLock — два хороших примера таких «ownable synchronizers», предлагаемых платформой.

Для получения более подробной информации по этому вопросу можно обратиться к этому
посту.

Потоки JVM

В следующей секции дампа содержится информация о технических потоках JVM, которые не являются частью приложения и связаны с потоками операционной системы. Т.к. эти потоки работают вне приложения, у них нет идентификаторов потока. Чаще всего это потоки сборщика мусора и другие технические потоки JVM:

Глобальные ссылки JNI

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


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

Взаимно заблокированные (Deadlocked) потоки


В первом подразделе описывается сценарий взаимной блокировки (deadlock):

Поток Thread-0 ожидает возможность захватить монитор (это обращение к блоку synchronized(secondResource) в нашем приложении), в то же время этот поток удерживает монитор, который пытается захватить поток Thread-1 (это обращение к тому же фрагменту кода: synchronized(secondResource) в нашем приложении).

Эта циклическая блокировка по другому называется deadlock. На рисунке ниже
эта ситуация представлена в графическом виде:


Во втором подразделе для обоих заблокированных потоков приведен stack trace.

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


Это приложение завершится без взаимной блокировки, и в качестве результата мы получим следующий вывод (обратите внимание на то, что адреса экземпляров класса Object изменились):

Анализ более сложных Thread Dump-ов

Дампы настоящих приложений могут быть очень большими и сложными.

В одной JVM одновременно могут быть запущены сотни тысяч потоков. И заблокированными могут оказаться больше чем два потока (или может быть несколько проблем многопоточности, вызванных одной проблемой).

Анализ такого громадного объема информации может стать настоящей проблемой.

Для анализа больших дампов предназначены специальные утилиты-анализаторы — Thread Dump Analyzers (TDAs). Эти утилиты парсят Java thread dump-ы и выводят информацию в человеко-читаемом виде, часто с применением графических средств. Более того, некоторые из них могут выполнить статический анализ и найти причину проблемы. Конечно, выбор конкретной утилиты зависит от целого ряда обстоятельств.

Тем не менее приведем список наиболее популярных TDA:

Заключение

Thread dump-ы — это отличное средство анализа состояния Java-приложения, особенно в случаях неожиданного поведения многопоточных приложений. Однако без надлежащего багажа знаний, дампы могут внести дополнительную сложность и в без того сложную ситуацию.

В этой статье мы разработали приложение с deadlock, сформировали дамп зависшего приложения. Проанализировали дамп и нашли причину блокировки и исправили её. Это не всегда так просто, для анализа большинства реальных приложений очень полезно использовать специальные утилиты — анализаторы.

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

Хотя, thread dump — это не «серебряная пуля» в мире многопоточности, тем не менее это важное средство диагностирования сложных, но довольно распространенных проблем многопоточных Java-приложений.

В программе курса Разработчик Java вопросы многопоточности занимают заметную часть. Мы детально рассматриваем как разрабатывать программы так, чтобы не приходилось по ночам разбираться с deadlock-в продакшене.

Как всегда интересны ваши мнения и комментарии, которые можно оставить тут или заглянуть к Виталию на день открытых дверей.

Теперь мне нужно знать pid процесса, который я только что начал.

Для этого пока нет общедоступного API. см. Солнце Ошибка 4244896 , Солнце Ошибка 4250622

В качестве обходного пути:

возвращает объект типа

Класс Process является абстрактным, и вы получаете некоторый подкласс Process, разработанный для вашей операционной системы. Например, на Mac он возвращает Java.lang.UnixProcess , который имеет закрытое поле с именем pid . Используя Reflection, вы можете легко получить значение этого поля. Это по общему признанию взломать, но это могло бы помочь. Для чего вам нужен PID ?

На этой странице есть HOWTO:

Возвращает экземпляр «Java.lang.Win32Process») OR «Java.lang.ProcessImpl»

Оба имеют приватное поле «ручка».

Это дескриптор ОС для процесса. Вам нужно будет использовать этот + Win32 API для запроса PID. На этой странице есть подробности о том, как это сделать.

В системе Unix (Linux и Mac)

Поскольку Java 9 класс Process имеет новый метод long pid() , поэтому он так же прост, как

Включите jna (и "JNA", и "JNA Platform") в вашу библиотеку и используйте эту функцию:

Вы также можете скачать JNA из здесь и JNA Platform из здесь .

Я думаю, что я нашел решение, которое выглядит довольно пуленепробиваемым при работе на большинстве платформ .. Вот идея:

  1. Создайте мьютекс всей JVM, который вы приобретаете, прежде чем порождать новый процесс/убивать процесс
  2. Используйте платформо-зависимый код для получения списка дочерних процессов + pids вашего процесса JVM
  3. Порождает новый процесс
  4. Получите новый список дочерних процессов + pids и сравните с предыдущим списком. Тот, кто нов, - твой парень.

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

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

Ниже приведена реализация вышеуказанной идеи с использованием JavaSysMon library. Это

В моем тестировании все классы IMPL имели поле «pid». Это сработало для меня:

Просто убедитесь, что возвращаемое значение не -1. Если это так, то проанализируйте вывод ps .

Я использовал непереносимый подход для извлечения PID UNIX из объекта Process , за которым очень просто следовать.

ШАГ 1: Используйте некоторые вызовы API Reflection для определения класса реализации Process на JRE целевого сервера (помните, что Process является абстрактным классом). Если ваша UNIX-реализация похожа на мою, вы увидите класс реализации, у которого есть свойство с именем pid , содержащее PID процесса. Вот код регистрации, который я использовал.

ШАГ 2: Основываясь на классе реализации и имени поля, которые вы получили из журнала отражений, напишите некоторый код, чтобы украсть класс реализации Process , и получить PID из него с помощью API Reflection. Приведенный ниже код работает для меня на мой вкус UNIX. Возможно, вам придется настроить константы EXPECTED_IMPL_CLASS_NAME и EXPECTED_PID_FIELD_NAME , чтобы она работала для вас.

Это не общий ответ.

Однако: некоторые программы, особенно сервисы и долго работающие программы, создают (или предлагают создать, необязательно) «файл pid».

Например, LibreOffice предлагает --pidfile= , см. docs .

Я довольно долго искал решение для Java/Linux, но PID (в моем случае) был под рукой.

Нет простого решения. То, как я делал это в прошлом, - это запустить другой процесс, чтобы запустить команду ps в Unix-подобных системах или команду tasklist в Windows, а затем проанализировать вывод этой команды для требуемого PID. В действительности я закончил тем, что поместил этот код в отдельный сценарий Shell для каждой платформы, которая просто возвращала PID, чтобы я мог сохранить часть Java как независимую от платформы. Это не очень хорошо для краткосрочных задач, но это не проблема для меня.

Для систем GNU/Linux & MacOS (или вообще UNIX-подобных) я использовал метод, который работает ниже:

Я считаю, что единственный переносимый способ сделать это - запустить (дочерний) процесс через другой (родительский) Java-процесс, который сообщит мне фактический PID родительского процесса. Дочерний процесс может быть чем угодно.

Код этой обертки

Чтобы использовать его, создайте jar-файл только с этим и вызовите его с аргументами команды:

jnr-process project предоставляет эту возможность.

Он является частью нативной среды выполнения Java, используемой jruby, и может рассматриваться как прототип для будущего Java-FFI

Одним из решений является использование уникальных инструментов, предлагаемых платформой:

Отказ от ответственности: не проверено, но вы поняли:

  • Вызовите ps для получения списка процессов,
  • Найдите свой, потому что вы знаете команду, с которой вы его запустили.
  • Если есть несколько процессов с одной и той же командой, вы можете:
    • Добавьте еще один фиктивный аргумент, чтобы отличить их
    • Положитесь на увеличение PID (не совсем безопасно, не одновременно)
    • Проверьте время создания процесса (может быть слишком грубым, чтобы действительно дифференцировать, а также не одновременно)
    • Добавьте определенную переменную среды и перечислите ее с помощью ps .

    Если переносимость не является проблемой, и вы просто хотите получить pid в Windows без особых хлопот при использовании кода, который протестирован и работает на всех современных версиях Windows, вы можете использовать библиотеку kohsuke winp . Это также доступно на Центральном Maven для легкого потребления.

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

    Мне нужно найти PID текущего запущенного процесса на платформе Linux (это может быть зависимое системное решение). Java не поддерживает получение идентификатора процесса, и JRuby в настоящее время имеет ошибку с методом Ruby, Process.пид-регулятор.

    есть ли другой способ получить PID?

    Если у вас procfs установлен, вы можете найти идентификатор процесса через символическую ссылку /proc/self, которая указывает на каталог, имя которого является pid (здесь также есть файлы с другой соответствующей информацией, включая PID, но каталог-это все, что вам нужно в этом случае).

    таким образом, с Java, вы можете сделать:

    в JRuby вы можете использовать одно и то же решение:

    тестируется только в Linux с помощью Sun JVM. Может не работать с другими реализациями JMX.

    вы можете использовать интерфейс JNI для вызова функции POSIX getpid(). Это довольно прямолинейно. Вы начинаете с класса для функций POSIX, которые вам нужны. Я называю это POSIX.java :

    скомпилировать его с

    после этого вы создаете заголовочный файл POSIX.h С

    файл заголовка содержит прототип C для функции с обертыванием функции getpid. Теперь вам нужно реализовать функцию, которая довольно простой. Я сделал это в POSIX.c :

    теперь вы можете скомпилировать его с помощью gcc:

    вы должны указать место где установлена платформа Java. Вот и все. Теперь вы можете использовать его. Создайте простую программу getpid:

    скомпилировать его с javac getpid.java и запустить его:

    первый pid написан оболочкой, а второй написан программой Java после того, как приглашение оболочки вернулось. ∎

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

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

    Это может заменить последнюю строку в массиве, заданном exec звонок.

    вы можете попробовать getpid() в JNR-Posix.

    Он также имеет оболочку Windows POSIX, которая вызывает getpid () от libc. Нет необходимости в JNI.

    Java 9 наконец предлагает официальный способ сделать это с ProcessHandle:

    Я работаю над проектом Gradle Java. Который запускает Tomcat для тестирования и останавливает его позже. Я должен убить этот экземпляр Tomcat, когда тест не пройден. Я попытался использовать команду «ps aux | grep tomcat | grep -v grap | awk », чтобы получить идентификатор процесса и завершить процесс. Но в Production будет много процессов Tomcat, запущенных одновременно многими пользователями, и я просто хочу, чтобы процесс tomcat был запущен моим build.gradle для выполнения теста. Итак, как я могу выполнить задачу? Пожалуйста, дайте мне несколько рекомендаций.

    Правильный способ завершения экземпляра Tomcat - через собственную команду shutdown . Вы вообще не должны думать о процессах, PID или убийствах.

    Вы можете использовать переменную оболочки $! . Он представляет PID самой последней фоновой команды.

    Java JRE имеет инструмент под названием jps в папке $JAVA_HOME/bin . Это похоже на команду unix ps , но только для Java. Вы можете использовать его для определения необходимого вам процесса Java. Использование этого инструмента более рекомендуется, и на самом деле это более полезно, когда на вашем хосте запущено более одного java-приложения .

    Например, у меня работает база данных h2 и много других приложений, но я хочу убить только h2, поэтому я могу использовать jps, чтобы получить его PID

    А затем просто убить нужный процесс:

    И все другие приложения Java будут продолжать работать в обычном режиме. Я не уверен насчет tomcat, но я думаю, что это можно сделать подобным образом, что-то вроде этого:

    Наконец, немного не по теме, но особенно для вашего случая: правильным способом было бы использовать сценарии запуска / остановки / перезапуска, предоставленные tomcat

    Вам нужно найти уникальную строку в выводе «ps aux», которая отличает вашего тестового кота от других.

    В настоящее время я использую приведенный ниже скрипт, чтобы сначала запустить shutdown.sh, а затем уничтожить PID, поскольку в большинстве случаев приложение останавливается, но процесс не останавливается.

    Вы также можете посмотреть на настройку файла PID, отредактировав файл «catalina.sh», который вы можете прочитать позже, чтобы узнать свой PID.

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

    Таким образом, с помощью ps -ef я перечисляю все процессы, затем grep -v grep удалит процесс команды grep, затем grep whoami будет искать вашего вошедшего в систему пользователя, а затем grep tomcat будет искать только процесс tomcat, протестируйте его один раз. и если все хорошо, то вы можете убить его.

    Кстати, как насчет сценария остановки tomcat? В случае, если это там, вы можете использовать это также.

    Читайте также: