Захват и анализ дампа потока в Java

В Java дампы потоков предоставляют подробный снимок многопоточной среды приложения. Понимание дампов потоков имеет решающее значение для устранения таких проблем, как высокая загрузка ЦП, взаимоблокировки или неотзывчивые приложения.

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

1. Краткое введение в потоки в Java

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

«Поток» в программе относится к отдельному пути выполнения. JVM предоставляет возможность приложению запускать несколько потоков одновременно. Каждому потоку назначается приоритет(по умолчанию 5), и потоки с более высокими приоритетами получают предпочтение при выполнении по сравнению с потоками с более низкими приоритетами.

Каждый поток имеет уникальный идентификатор и имя и может быть отнесен к одной из следующих категорий:

  • Демонический поток
  • Недемонический поток

Поток-демон работает независимо от других потоков в системе и завершается только после вызова метода Runtime.exit()(и разрешения менеджером безопасности на выход из программы) или завершения всех потоков, не являющихся демонами.

2. Что такое дамп потока?

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

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

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

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

3. Настройка демонстрационной программы

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

public class ThreadDeadlock {private static final Object lock1 = new Object();private static final Object lock2 = new Object();public static void main(String[] args) {Thread thread1 = new Thread(() -> {synchronized(lock1) {System.out.println("Thread1: Holding lock1...");try { Thread.sleep(100); } catch(InterruptedException ignored) {}System.out.println("Thread1: Waiting for lock2...");synchronized(lock2) {System.out.println("Thread1: Acquired lock2.");}}});Thread thread2 = new Thread(() -> {synchronized(lock2) {System.out.println("Thread2: Holding lock2...");try { Thread.sleep(100); } catch(InterruptedException ignored) {}System.out.println("Thread2: Waiting for lock1...");synchronized(lock1) {System.out.println("Thread2: Acquired lock1.");}}});thread1.start();thread2.start();try {thread1.join();thread2.join();} catch(InterruptedException e) {e.printStackTrace();}}}

Теперь, когда наша демонстрационная программа готова, мы готовы создать дамп потока нашего кода и подробно его проанализировать.

4. Различные способы получения дампа потока

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

4.1.Jstack

Jstack — это утилита командной строки JDK, которую мы можем использовать для захвата дампа потока. Она берет PID процесса и отображает дамп потока в консоли. В качестве альтернативы мы можем перенаправить его вывод в файл.

Мы можем использовать следующую команду для получения дампов потоков с помощью jstack.

jstack 2068 > /tmp/threaddump.txt

Здесь команда jstack создает дампы потоков с указанным идентификатором(2068) и сохраняет выходной файл(threaddump.txt) в папке /tmp. PID может быть разным для каждого процесса.

4.2. Команда «Убить»

Как мы знаем, Linux в основном используется при развертывании в производственных условиях, а по соображениям безопасности у нас есть только JRE, а не JDK, поэтому для создания дампов потоков нам требуется решение командной строки.

Самый простой способ захватить дамп потока в Unix-подобных системах — это использовать команду kill, которую мы можем использовать для отправки сигнала процессу с помощью системного вызова kill(). В этом случае мы отправим ему сигнал -3.

Используя тот же PID из предыдущих примеров, давайте рассмотрим, как использовать kill для захвата дампа потока:

kill -3 2068 > /tmp/threaddump.txt

4.3.JProfiler

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

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

Шаг 1. Откройте JProfiler и прикрепите свою программу, выбрав значок «Прикрепить» в главном меню, как показано ниже.

Захват и анализ дампа потока в Java0

Шаг 2. Щелкните значок Capture в разделе Thread -> Thread Dumps.

Захват и анализ дампа потока в Java1

После нажатия кнопки «Захват» вы заметите, что для выбранного PID создаются дампы потоков.

Захват и анализ дампа потока в Java2

4.4.VisualVM

VisualVM — это мощный инструмент, предоставляющий визуальный интерфейс для просмотра подробной информации о локальных и удаленных приложениях Java во время их работы на виртуальной машине Java(JVM).

Чтобы создать или захватить дампы потоков в Visual VM, выполните следующие действия:

Шаг 1. Выберите PID, для которого вы хотите сгенерировать дампы потоков(все PID отображаются на локальной вкладке).

Шаг 2. После выбора PID просто щелкните правой кнопкой мыши и выберите дамп потока, это сгенерирует дампы потока.

Захват и анализ дампа потока в Java3

5. Понимание формата и структуры файла дампа потока

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

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

  1. Общая информация: этот раздел находится в верхней части файла и обычно содержит временную метку файла дампа потока, когда он был создан.
  2. Информация о безопасном высвобождении памяти(SMR): это список потоков, в котором перечислены адреса всех внутренних потоков, не относящихся к JVM(например, не относящихся к VM и не относящихся к сборке мусора(GC)).
Захват и анализ дампа потока в Java4
  1. Потоки: После информации SMR у нас есть список потоков, которые захвачены, и это основной список, который нам нужно проанализировать для нашего приложения. Теперь давайте взглянем на информацию, которую мы можем извлечь, просматривая поток.

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

    • Имя: понятное для человека имя потока. Это имя можно задать, вызвав метод setName для Threadobject и получить, вызвав getName для объекта.
    • ID: Уникальный идентификатор, связанный с каждым объектом Thread. Этот номер генерируется, начиная с 1, для всех потоков в системе. Этот идентификатор доступен только для чтения и может быть получен путем вызова getId для объекта Thread.
    • Статус демона/Тип потока: Тег, указывающий, является ли поток потоком демона. Если поток является демоном, этот тег будет присутствовать; если поток не является потоком демона, тег не будет присутствовать.
    • Приоритет: числовой приоритет потока Java. Однако он не обязательно соответствует приоритету потока ОС, которому отправляется поток Java.
    • Приоритет потока ОС: этот приоритет может отличаться от приоритета потока Java и соответствует потоку ОС, в который отправляется поток Java.
    • Адрес: этот адрес представляет собой адрес указателя собственного объекта Thread интерфейса Java Native Interface(JNI)(объект потока C++, который поддерживает поток Java через JNI).
    • Идентификатор потока ОС: уникальный идентификатор потока ОС, с которым сопоставлен поток Java.
    • Статус: Человекочитаемая строка, отображающая текущий статус потока. Эта строка предоставляет информацию, выходящую за рамки базового состояния потока(см. ниже), и может быть полезна для обнаружения предполагаемых действий потока.

Надеюсь, что до сих пор у вас есть четкое представление о чтении потока в файле дампа потока. Давайте посмотрим на изображение ниже, которое развеет ваши сомнения, если они у вас есть.(См. только пункт номер 3.)

Захват и анализ дампа потока в Java5

Если у вас остались вопросы, то пишите нам в комментариях. Мы будем рады ответить на ваш запрос.

Теперь быстро перейдем к пунктам 4 и 5, чтобы понять формат файла дампа потока.

  1. Трассировка стека потока: за ней всегда следует информация о потоке, указанная в первой строке, и она предоставляет последнее дерево стека о потоке, которое содержит очень полезную информацию о состояниях потока.
  2. Заблокированный принадлежащий синхронизатор: это последняя часть информации о потоке, содержащая список синхронизаторов(объектов, которые можно использовать для синхронизации, например, блокировок), которые принадлежат исключительно потоку.

Все пункты от 3 до 5 дают полную информацию о потоке, которая очень полезна при анализе дампов потоков.

6. Потоки JVM: дамп потока содержит внутренние потоки JVM(не приложения), которые привязаны к ОС. Поскольку эти потоки не существуют в приложении Java, у них нет идентификатора потока. Эти потоки обычно состоят из потоков GC и других потоков, используемых JVM для запуска и обслуживания приложения Java.

Захват и анализ дампа потока в Java6

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

6. Анализ дампов потоков Java вручную

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

Здесь мы сосредоточимся только на двух проблемах:

6.1 Выявление тупиковых ситуаций

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

"Thread-0" #13 prio=5 os_prio=0 cpu=0.00ms elapsed=161.89s tid=0x000001b576d9c800 nid=0x3368 waiting for monitor entry [0x000000b4911ff000]java.lang.Thread.State: BLOCKED(on object monitor)at org.example.Threaddeadlock.lambda$main$0(Threaddeadlock.java:13)- waiting to lock <0x00000007110ee670>(a java.lang.Object)- locked <0x00000007110ee660>(a java.lang.Object)at org.example.Threaddeadlock$$Lambda$14/0x0000000800066840.run(Unknown Source)at java.lang.Thread.run(java.base@11.0.15.1/Thread.java:834)Locked ownable synchronizers:- None"Thread-1" #14 prio=5 os_prio=0 cpu=0.00ms elapsed=161.89s tid=0x000001b576d5d800 nid=0x2c88 waiting for monitor entry [0x000000b4912ff000]java.lang.Thread.State: BLOCKED(on object monitor)at org.example.Threaddeadlock.lambda$main$1(Threaddeadlock.java:24)- waiting to lock <0x00000007110ee660>(a java.lang.Object)- locked <0x00000007110ee670>(a java.lang.Object)at org.example.Threaddeadlock$$Lambda$15/0x0000000800066c40.run(Unknown Source)at java.lang.Thread.run(java.base@11.0.15.1/Thread.java:834)Locked ownable synchronizers:- None

Итак, из приведенного выше фрагмента дампа потока мы видим, что потоки Thread-0 и Thread-1 находятся в состоянии «ЗАБЛОКИРОВАНО» и не могут быть освобождены в данный момент, поэтому они создают взаимоблокировку в нашем приложении, что влияет на производительность приложения.

6.2 Выявление высокой загрузки ЦП

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

"Thread-0" #13 prio=5 os_prio=0 cpu=5187.50ms elapsed=5.24s tid=0x000001cff60f3800 nid=0x4354 runnable [0x0000009031afe000]java.lang.Thread.State: RUNNABLEat org.example.ThreadhighCPU.lambda$main$0(ThreadhighCPU.java:10)at org.example.ThreadhighCPU$$Lambda$14/0x0000000800066840.run(Unknown Source)at java.lang.Thread.run(java.base@11.0.15.1/Thread.java:834)Locked ownable synchronizers:- None

Вы заметили, что мы только что обсудили? Вы можете легко увидеть, что поток потребляет много ресурсов ЦП(5187,50 мс) и его состояние — Runnable.

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

7. Анализ дампов потоков Java с помощью инструментов

7.1. FastThread.io [Платный инструмент]

Для работы со сложными дампами потоков мы можем использовать некоторые инструменты Thread Analyzer. В Market есть много инструментов для TDA, однако мы собираемся обсудить 'Fast Thread', один из лучших онлайн-инструментов для анализа дампов потоков в реальном времени.

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

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

Шаг 1. Запустите fastThread, используя URL: http://fastthread.io/

Шаг 2. Создайте учетную запись с бесплатным планом(см. раздел «Цены» в соответствии с вашими потребностями).

Шаг 3. После создания учетной записи загрузите файл дампа потока и нажмите кнопку «Анализ».

Захват и анализ дампа потока в Java7

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

Некоторые фрагменты экрана из отчета о быстрой теме.

Захват и анализ дампа потока в Java8
Захват и анализ дампа потока в Java9

7.2 VisualVM [Бесплатный инструмент]

Теперь давайте обсудим еще один инструмент для анализа сбоев потоков — VisualVM.

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

Некоторые скриншоты VisualVM.

Захват и анализ дампа потока в Java10
Захват и анализ дампа потока в Java11
Захват и анализ дампа потока в Java12

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

8. Сравнение инструментов для анализа дампов потоков

Теперь давайте сравним инструменты и методы, которые мы использовали до сих пор в этой статье, на основе их плюсов и минусов.

Инструменты Плюсы Минусы
Jstack Входит в состав JDK, поэтому легко доступен.
Прост в использовании и запускается из командной строки.
Работает на любой платформе, поддерживающей Java.
Ограниченные возможности по сравнению с некоторыми сторонними инструментами.
При возникновении проблемы требуется ручное выполнение.
VisualVM Поставляется с JDK, поэтому доступен без дополнительной установки.
Предлагает удобный графический интерфейс для мониторинга и сбора дампов потоков.
Предоставляет другие функции анализа производительности, включая дампы кучи и профилирование.
Для подключения к целевому приложению может потребоваться некоторая настройка.
убить -3 Простой и быстрый метод захвата дампов потоков в системах на базе Unix/Linux Доступно только в системах на базе Unix.
Предоставляет дампы потоков для всего процесса Java, а не для конкретного приложения Java.
JProfiler Расширенные возможности профилирования, включая захват дампа потока.
Может предоставить подробную информацию об активности потоков и узких местах производительности.
Коммерческие инструменты, требующие лицензии.
Избыточность для базовых нужд захвата дампа потока
FastThread Инструмент с открытым исходным кодом для анализа дампов потоков.
Обеспечивает графическое представление активности потока для более легкого анализа.
Позволяет эффективно фильтровать, искать и перемещаться по дампам потоков.
Требуется платная лицензия, если у вас более 5 дампов потоков в месяц.
Анализатор дампа потока Инструмент с открытым исходным кодом для анализа дампов потоков.
Обеспечивает графическое представление активности потока для более легкого анализа.
Позволяет эффективно фильтровать, искать и перемещаться по дампам потоков.
Требуется установка и знакомство с инструментом.

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

9. Лучшие практики

Теперь давайте обсудим некоторые из лучших практик при создании и чтении дампов потоков.

  • Тщательно собирайте дампы потоков: собирайте дампы потоков в периоды проблем с производительностью или когда вы подозреваете проблемы с потоками. Нет смысла собирать дампы, когда нет проблем или прошло время.
  • Используйте инструменты анализа дампа потока: рассмотрите возможность использования специализированных инструментов анализа дампа потока, таких как Thread Dump Analyzer(TDA), FastThread(если доступно) или других аналогичных инструментов для более эффективного и всестороннего анализа.
  • Визуализация состояний потоков: большинство инструментов анализа предоставляют визуальные представления состояний потоков. Понимание состояний потоков является фундаментальной частью анализа.
  • Изучите трассировки стека потоков: сосредоточьтесь на методах и разделах кода, где потоки тратят значительное количество времени.
  • Агрегация и суммирование данных: агрегация данных, связанных с потоками, таких как количество и состояние потоков, для получения обзора поведения потоков приложения.
  • Мониторинг с течением времени: собирайте и анализируйте дампы нескольких потоков с течением времени, чтобы выявить повторяющиеся закономерности или тенденции в поведении потоков вашего приложения.
  • Документируйте выводы и рекомендации: документируйте свой анализ, выводы и рекомендации по решению проблем. Эта документация будет полезна при общении с вашей командой или руководством. Это основная часть, которая позволяет избежать дублирования проблем.

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

10. Заключение

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

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

Исходный код на Github

Прокрутить вверх