ИТ Блог. Администрирование серверов на основе Linux (Ubuntu, Debian, CentOS, openSUSE)

Устранение проблем с высокой загрузкой процессора Java

Устранение проблем с высокой загрузкой процессора Java

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

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

 

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

 

Рекомендации по предотвращению проблем с высокой загрузкой процессора

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

Тщательно обрабатывайте исключения

Исключения отнимают много процессорного времени, когда они генерируются. Это связано с тем, что исключения прерывают обычный поток программы, и Java runtime environment (JRE) необходимо повторно просмотреть фреймы вызовов, которые привели к событию throw, чтобы собрать данные для события exception. В приложениях, где вы создаете слишком много исключений, ваше приложение может тратить слишком много своих ресурсов на обработку этих исключений вместо своей реальной бизнес-логики.

Исключения часто могут связываться с другими источниками высокой загрузки ресурсов, такими как потоковая передача. Например, метод HTTPResponse.Redirect выдает ThreadAbortException, который приводит к завершению основного потока при возникновении исключения. Если вы создадите свое приложение так, чтобы оно слишком часто выдавало это исключение, ваше приложение будет тратить много времени на удаление и создание новых потоков, что увеличит вашу высокую загрузку процессора.

Сократите накладные расходы на обработку

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

Обращайтесь с потоками осторожно

Многопоточность была как благом, так и проклятием для программирования на Java. При неправильном обращении потоки могут снизить ресурсоэффективность вашего приложения, забивая ресурсы и делая невозможным отслеживание и завершение (см. Процессы-зомби). Чтобы убедиться, что загрузка ЦП Java не зашкаливает, убедитесь, что вы завершаете потоки, когда заканчиваете с ними, и что код, ответственный за очистку потоков, не недоступен в любой момент времени (из-за каких-либо недостатков дизайна).

Не игнорируйте свою память

Хотя это может показаться несвязанным, плохое управление памятью также может привести к высокой загрузке процессора. Если ваше Java-приложение использует большой процент доступной памяти, потребуются более частые циклы сборки мусора (GC), чтобы предотвратить полную нехватку памяти в приложении. Каждый запуск сборки мусора увеличивает общую загрузку процессора JVM.

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

 

Как выявить проблемы в производственной среде

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

Запланированные проверки и запросы

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

Системные инструменты

Инструменты системного мониторинга и просмотра процессов, такие как top, htop и другие, полезны для понимания потребления ЦП JVM или отдельными потоками Java. Если вы активно расследуете инцидент с высокой загрузкой процессора, вы часто будете обращаться к этим инструментам.

Инструменты профилировщика

Инструменты профилирования – это продвинутые инструменты, предназначенные для получения глубокого представления данных о производительности ваших потоков Java и приложений. Профилировщики могут предоставить гораздо больше, чем просто показатели загрузки ЦП – они могут помочь вам визуализировать дерево вызовов распределения, дерево вызовов взаимодействий с вашей базой данных, использование оперативной памяти, количество экземпляров объектов и многое другое. Вы можете легко исследовать такие проблемы, как медленное взаимодействие с базой данных, “раздувание” памяти и частые вызовы GC, которые часто напрямую способствуют высокой загрузке процессора. Сделать это довольно сложно с помощью ранее упомянутых базовых инструментов.

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

Инструменты мониторинга

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

 

Как выяснить причину высокой загрузки процессора Java

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

Проверьте идентификатор процесса, потребляющего чрезмерные ресурсы

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

Обзор загрузки процессора/дампов потоков

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

Просмотрите необработанные данные

Получив доступ к дампам потоков, вы должны просмотреть данные, чтобы рассчитать загрузку процессора каждым потоком и определить поток, который потребляет больше всего ресурсов. Вы можете использовать такие инструменты, как IBM Thread and Monitor Dump Analyzer или найти значения вручную и рассчитать коэффициент загрузки процессора.

 

Инструменты для выявления проблем с загрузкой процессора Java

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

JVisualVM

Java VisualVM – это легкий инструмент, который может помочь вам визуализировать производительность вашего приложения. Внутренне он объединяет информацию из независимых инструментов, таких как jstack, jmap, jinfo и других, чтобы вы могли просматривать все данные, связанные с производительностью вашего приложения, в одном месте.

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

JVisualVM распространялся вместе с Oracle JDK в версиях 6-8. Его выпуск был прекращен в JDK 9, и теперь его можно установить как отдельный инструмент.

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

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

JProfiler

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

JProfiler уделяет приоритетное внимание простоте использования. Помимо интуитивно понятного пользовательского интерфейса, он предлагает бесперебойную поддержку JVM на базе Docker и Kubernetes. Удаленное профилирование упрощается благодаря встроенной функциональности SSH-туннеля. JProfiler выполняет все это, сохраняя при этом небольшие накладные расходы на хост-машине.

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

Prometheus

Prometheus – это комплексное решение для мониторинга, которое можно использовать для отслеживания и анализа производительности большинства типов приложений. Вы можете использовать JMX exporter чтобы предоставить данные о производительности для сбора в Prometheus.

Prometheus активно обрабатывает данные, предоставляемые JMX exporter, сохраняет их и позволяет настраивать запросы, графики и оповещения, чтобы помочь вам использовать собранные данные. Вы можете легко подключить Prometheus к инструменту визуализации, такому как Grafana, для создания мощных визуализаций и отчетов.

Prometheus – хороший вариант для ваших Java-приложений, если вы ищете систему мониторинга с открытым исходным кодом для анализа использования ресурсов ваших приложений в процессе производства или уже использовали Prometheus в некоторых других проектах. Самым большим недостатком Prometheus является то, что его может быть довольно сложно настроить и обслуживать. Если это вызывает серьезную озабоченность, возможно, вам придется рассмотреть платные корпоративные решения.

Graphite

Graphite – это еще один инструмент мониторинга с открытым исходным кодом, который можно использовать в широком спектре технологических стеков. Однако, в отличие от Prometheus, Graphite не предлагает полного набора инструментов для быстрого начала работы. Вы должны соединить его со StatsD и collectd для сбора показателей, Carbon для агрегирования, Whisper для хранения и Grafana для визуализации ваших данных.

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

По сравнению с Prometheus, Graphite имеет несколько постоянных проблем — в Graphite сложно настроить избыточность данных, совместную работу, обработку большого объема и гибкую агрегацию данных. В целом Graphite отлично подходит для небольших настроек, но при масштабировании может стать довольно сложным в обслуживании.

 

Инструмент мониторинга приложений Java также может отслеживать запрос по мере его прохождения через несколько микросервисов вашего приложения с помощью распределенной трассировки.

 

Пример устранения неполадок: идентификация кода, вызывающего высокую загрузку процессора

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

Эта Java-программа покажет высокую загрузку процессора:

public class Main { 

    // Функция Main создает новый поток для выполнения сложной операции и выполняет простую операцию печати в цикле 
    public static void main(String[] args) throws InterruptedException { 

        Thread thread = new Thread(() -> { 
            for (int i=0; ;i++) 
                heavyTask(i); 
        }); 

        thread.setName("Heavy Task Thread"); 

        thread.start();

        for (int i=0; ;i++) { 
            System.out.println("The numbers next to " + i + " are " + (i-1) +  " & " + (i+1)); 
            Thread.sleep(1); 
       } 
    } 

    // Простая, требующая больших затрат процессора функция, которая вычисляет факториалы из трех чисел подряд
              public static void heavyTask(int i) { 
        System.out.println("The factorial of " + i + " is " + fact(i)); 
        System.out.println("The factorial of " + (i + 1) + " is " + fact(i + 1)); 
        System.out.println("The factorial of " + (i + 2) + " is " + fact(i + 2)); 
    } 

    // Функция, которая вычисляет факториал заданного числа
    public static BigInteger fact(int num) { 
        BigInteger factorial = BigInteger.ONE; 
        for(int i = 1; i <= num; ++i) 
        { 
            // factorial = factorial * i; 
            factorial = factorial.multiply(BigInteger.valueOf(i)); 
        } 
       return factorial; 
    } 
}

 

Эта программа состоит из двух потоков, основного потока и потока с именем “Поток тяжелых задач”. Если вы запустите эту программу и проанализируете общее потребление ресурсов процессом в течение короткого времени (скажем, минуты) с помощью такого инструмента, как JVisualVM, вот что вы обнаружите:

Рис. 1: Обзор потребления ресурсов

Рис. 1: Обзор потребления ресурсов

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

Чтобы изучить его более внимательно, вы можете проанализировать дамп потока:

2023-03-06 04:27:14 
Full thread dump Java HotSpot(TM) 64-Bit Server VM (17.0.6+9-LTS-190 mixed mode, sharing): 

Threads class SMR info: 
_java_thread_list=0x000001c4444b72e0, length=21, elements= 
0x000001c41aefbe80, 0x000001c43db7c670, 0x000001c43db7d430, 0x000001c43db8c0e0, 
0x000001c43db8c9a0, 0x000001c43db8e360, 0x000001c43db8ef20, 0x000001c43db904d0, 
0x000001c43db9c890, 0x000001c43dba10c0, 0x000001c43dc9b080, 0x000001c43dd1ba50, 
0x000001c43dd23b10, 0x000001c43ddd0400, 0x000001c44157f780, 0x000001c443278670, 
0x000001c43f448e30, 0x000001c4402aaac0, 0x000001c4401f70b0, 0x000001c444ae87f0, 
0x000001c441af2130 

"main" #1 prio=5 os_prio=0 cpu=78.12ms elapsed=15.51s tid=0x000001c41aefbe80 
nid=0x6cb4 sleeping [0x000000a4211ff000] 
   java.lang.Thread.State: TIMED_WAITING (sleeping) 
        at java.lang.Thread.sleep(java.base@17.0.6/Native Method) 
        at org.example.Main.main(Main.java:27) 

   Locked ownable synchronizers: 
        - None
Heavy Task Thread" #16 prio=5 os_prio=0 cpu=6281.25ms elapsed=15.42s tid=0x000001c43ddd0400 nid=0x6c60 runnable  [0x000000a4225fe000] 
   java.lang.Thread.State: RUNNABLE 
        at java.math.BigInteger.multiplyByInt(java.base@17.0.6/BigInteger.java:1696) 
        at java.math.BigInteger.multiply(java.base@17.0.6/BigInteger.java:1612) 
        at java.math.BigInteger.multiply(java.base@17.0.6/BigInteger.java:1586) 
        at org.example.Main.fact(Main.java:36) 
        at org.example.Main.heavyTask(Main.java:44) 
        at org.example.Main.lambda$main$0(Main.java:18) 
        at org.example.Main$$Lambda$14/0x0000000800c01200.run(Unknown Source) 
        at java.lang.Thread.run(java.base@17.0.6/Thread.java:833) 

   Locked ownable synchronizers: 
        - None

 

Обычно вы обнаружите несколько потоков в дампе потока. Однако в приведенном выше примере показаны только два соответствующих потока для примера программы. Вы заметите, что в то время как “основной” поток занимал 78,12 мс процессорного времени, поток “Потока тяжелых задач” за то же время занимал 6 281,25 мс процессорного времени. Это означает, что “Поток интенсивных задач”, похоже, увеличивает время процессора по сравнению с другими потоками в этой программе.

Затем вы можете использовать сэмплер JVisualVM для выборки данных о потреблении процессора и памяти в течение короткого времени (скажем, 10-12 секунд):

Рис. 2: Пример ресурса из JVisualVM

Рис. 2: Пример ресурса из JVisualVM

 

На вкладке Образцы процессора вы найдете список активных потоков с указанием времени их работы. Вы найдете функциональную разбивку потребления процессора. Вы можете использовать эту разбивку, чтобы определить, что функция fact() занимала больше всего процессорного времени в потоке “Heavy Task Thread”, что имеет смысл, поскольку оно содержит код для факториальных вычислений с интенсивным использованием ЦП. Таким образом, вы можете использовать инструмент профилирования для выявления ситуации с высоким потреблением процессора и углубленного изучения ее и, в конечном итоге, найти первопричину.

 

Выводы

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

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

Exit mobile version