Как измерить производительность программы java

Обновлено: 07.07.2024

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

Сравнивая версию OLD с новой версией, новая версия примерно в 2 раза медленнее старой.

Я пытаюсь сузить то, что вызывает замедление, но я нахожу, что я определяю время для определенных циклов for, используя System.println с System.currentTimeMillis(). Это действительно очень утомительно.

Есть ли инструмент производительности Java, который поможет мне разобраться, почему NEW JAR примерно на 2 раза медленнее старого?

спросил(а) 2020-03-27T16:00:15+03:00 1 год, 7 месяцев назад

JProfiler имеет возможность сравнивать моментальные снимки CPU. Запишите выполнение для старого и нового файла JAR и сохраните моментальные снимки (если JVM завершает работу в конце, настройте триггер "JVM exit", который сохраняет снимок).

Затем откройте окно сравнения моментальных снимков с помощью "Сессия- > Сравнить снимки в новом окне" и добавьте два моментальных снимка. Сравнение с горячими точками будет выглядеть следующим образом (в этом случае устанавливается фильтр просмотра):

enter image description here

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

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

enter image description here

Отказ от ответственности: Моя компания разрабатывает JProfiler.

ответил(а) 2020-03-27T16:18:51.312667+03:00 1 год, 7 месяцев назад

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

Java поставляется со встроенным профайлером hprof, но также см. также:

ответил(а) 2020-03-27T16:00:15+03:00 1 год, 7 месяцев назад

В зависимости от продолжительности процесса я бы подумал о Visual VM 1.3.3. Если вы загрузите все плагины, вы сможете увидеть кучу, потоки, объекты и т.д. Это должно помочь, и это не будет стоить ни копейки.

Я полагаю, что он предполагает JVM Oracle/Sun.

ответил(а) 2020-03-27T16:00:15+03:00 1 год, 7 месяцев назад

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

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

По умолчанию Java предоставляет пакеты для этих вещей.

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

Это вводная статья про то, как следует делать тесты производительности на JVM языках (java, kotlin, scala и тд.). Она полезна для случая, когда требуется в цифрах показать изменение производительности от использования определенного алгоритма.

Все примеры приведены на языке kotlin и для системы сборки gradle. Исходный код проекта доступен на github.

КДВП

Подготовка

В первую очередь остановимся на основной части наших замеров — использовании JMH. Java Microbenchmark Harness — набор библиотек для тестирования производительности небольших функций (то есть тех, где пауза GC увеличивает время работы в разы).

Перед запуском теста JMH перекомпилирует код, так как:

  1. Для уменьшения погрешности вычисления времени работы функции необходимо запустить её N раз, подсчитать общее время работы, а потом поделить его на N.
  2. Для этого требуется обернуть запуск в виде цикла и вызова необходимого метода. Однако в этом случае на время работы функции повлияет сам цикл, а также сам вызов замеряемой функции. А потому вместо цикла будет вставлен непосредственно код вызова функции, без reflection или генерации методов в runtime.

После переделки байткода тестирование можно запустить командой вида java -jar benchmarks.jar , так как все необходимые компоненты уже будут запакованы в один jar файл.

JMH Gradle Plugin

Как понятно из описания выше, для тестирования производительности кода недостаточно просто добавить необходимые библиотеки в classpath и запустить тесты в стиле JUnit. А потому, если мы хотим делать дело, а не разбираться в особенности написания билд скриптов, нам не обойтись без плагина к maven/gradle. Для новых проектов преимущество остается за gradle, потому выбираем его.

Для JMH есть полуофициальный плагин для gradle — jmh-gradle-plugin. Добавляем его в проект:

Плагин автоматом создаст новый source set (это "набор файлов и ресурсов, которые должны компилироваться и запускаться вместе", прочитать можно или статью на хабре за авторством svartalfar, или же в официальной документации gradle). jmh source set автоматически ссылается на main, то есть получаем короткий алгоритм работы:

  1. Код, который мы будем изменять, пишем в стандартном main source set, там же, где и всегда.
  2. Код с настройкой и прогревом тестов пишем в отдельном source set. Именно его byte code и будет перезаписываться, сюда плагин добавит необходимые зависимости, в которых есть определения аннотация и тд.

Получаем следующую иерархию каталогов:

  • src
    • jmh / kotlin/ <Имя java пакета> / <код, запускающий тесты (и аннотированный JMH аттрибутами)>
    • main / kotlin / <Имя java пакета> / <код для тестирования>

    Или как это выглядит в IntelliJ Idea:

    JMH Source Set в IntelliJ Idea

    В итоге, после настройки проекта, можно запускать тесты простым вызовом .\gradlew.bat jmh (или .\gradlew jmh для Linux, Mac, BSD)

    С плагином есть пара интересных особенностей на Windows:

    Тестирование

    В качестве примера я возьму вопрос (ранее заданный на kotlin discussions), который мучал меня ранее — зачем в конструкции use используется inline метод?

    Пример java кода:

    В kotlin есть полный аналог, который имеет немного другой синтаксис:

    То есть, как видно:

    1. Use — это просто метод-расширение, а не отдельная конструкция языка
    2. Use является inline методом, то есть одни и те же конструкции встраиваются в каждый метод, что увеличивает размер байткода, а значит в теории JIT`у будет сложнее оптимизировать код и т.д. И вот эту теорию мы и будем проверять.

    Итак, необходимо сделать два метода:

    1. Первый будет просто использовать use, который поставляется в библиотеке kotlin
    2. Второй будет использовать те же методы, однако без inline. В итоге на каждый вызов в куче будет создаваться объект с параметрами для лямбды.

    Код с JMH аттрибутами, который будет запускать разные функции:

    Dead Code Elimination

    Java Compiler & JIT довольно умные и имеют ряд оптимизаций, как в compile time, так и в runtime. Метод ниже, например, вполне может свернуться в одну строку (как для kotlin, так и для java):

    И в итоге мы будем тестировать метод:

    Однако результат ведь никак не используется, потому компиляторы (byte code + JIT) в итоге вообще выкинут метод, так как он в принципе не нужен.

    Чтобы избежать этого, в JMH существует специальный класс "черная дыра" — Blackhole. В нем есть методы, которые с одной стороны не делают ничего, а с другой стороны — не дают JIT выкинуть ветку с результатом.

    А для того, чтобы javac не пытался сложить-таки a и b в процессе компиляции, нам требуется определить объект state, в котором будут храниться наши значения. В итоге в самом тесте мы будем использовать уже подготовленный объект (то есть не тратим время на его создание и не даем компилятору применить оптимизации).

    В итоге для грамотного тестирования нашей функции требуется её написать вот в таком виде:

    Здесь мы взяли a и b из некоторого state, что помешает компилятору сразу посчитать выражение. А результат мы отправили в черную дыру, что помешает JIT выкинуть последнюю часть функции.

    Возвращаясь к моей функции:

    1. Объект для вызова метода close я создам в самом тесте, так как практически всегда при вызове метода close у нас до этого создавался объект.
    2. Внутри нашего метода придется вызывть функцию из blackhole, чтобы спровоцировать создание лямбды в куче (и не дать JIT выкинуть потенциально ненужный код).

    Результат теста

    Запустив ./gradle jmh , а потом подождав два часа, я получил следующие результаты работы на моем mac mini:

    Или, если сократить таблицу:

    В результате есть две самые важные метрики:

    1. Inline метод показал производительность 11,6 * 10^6 ± 0,02 * 10^6 операций в секунду.
    2. Lambda-based метод показал производительность 11,5 * 10^6 ± 0,04 * 10^6 операций в секунду.
    3. Inline метод в итоге работает быстрее и стабильнее по скорости. Возможно, увеличенная погрешность для lambdaUse связана с более активной работой с памятью.
    4. Я таки был неправ на том форуме — в стандартной библиотеке kotlin лучше оставить текущую реализацию метода.

    Заключение

    При разработке ПО есть два довольно частых способа сравнения производительности:

    1. Замер скорости работы цикла с N итерациями подопытной функции.
    2. Философские рассуждения вида "я уверен, что быстрее использовать сдвиг, чем умножение на 2", "сколько я программирую, всегда XML сериализация была самой быстрой" и тд.

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

    Apache Netbeans может использоваться не только как IDE, но и как отладчик. Работает во всех операционных системах, поддерживающих Java: Windows, Linux, Mac OSX и BSD.

    Apache Skywalking — инструмент мониторинга производительности приложений для распределенных систем, специально разработанный для микросервисов, облачных и контейнерных (Docker, Kubernetes, Mesos) архитектур, включая Java.

    Когда дело доходит до бесплатных инструментов для мониторинга приложений Java, обычно выбирают JConsole и VisualVM. AppDynamics предоставляет третий вариант, который поможет вам в мониторинге производительности веб-приложений.

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

    Flamegraph Datadog анализирует каждый запрос Java для обнаружения и устранения проблем с производительностью приложений в распределенной системе.

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

    Eclipse Memory Analyzer — набор инструментов общего назначения для анализа дампов кучи Java. Также он сообщает о возможных утечках и антипаттернах потребления памяти. Основная область применения — ошибки, связанные с нехваткой или повышенным потреблением памяти.

    Fusion Reactor — инструмент мониторинга Java, который позволяет находить проблемы с производительностью в Java-приложении или сервере, анализируя код, поток и память.

    GCeasy — первый инструмент для анализа журнала сборки мусора, работающий на основе машинного обучения. GCeasy автоматически обнаруживает проблемы в журналах JVM и Android GC и рекомендует вариант их решения.

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

    JavaMelody выполняет мониторинг приложений Java или Java EE в средах контроля качества и производственных средах. Это не инструмент для имитации запросов от пользователей — это инструмент для измерения и расчета статистики реальной работы приложения в зависимости от степени его использования.

    Подключаемый модуль Jet Brains Memory View показывает общее количество объектов в куче, сгруппированных по имени их класса. Когда вы пошагово выполняете программу, столбец Diff показывает, как количество объектов изменяется между остановками отладчика. Таким образом, вы можете легко увидеть, как код влияет на кучу.

    JProfiler — простой и мощный инструмент профилирования. Настройка сеансов в нем проста, сторонние интеграции упрощают начало работы, а данные профилирования представлены в естественном виде.

    JRat Java Runtime Analysis Toolkit — профилировщик производительности с открытым исходным кодом для платформы Java. JRat отслеживает выполнение приложения и сохраняет измерения производительности. Затем эти данные можно просмотреть и проанализировать с помощью JRat Desktop, приложения Swing.

    С помощью инструмента Kamon можно определить причину медленных запросов и другие ошибки. Разработчику достаточно навести и нажать мышь, чтобы найти endpoints, запросы к базе данных и вызовы API, которые влияют на взаимодействие с пользователем.

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

    Applications Manager JRE отслеживает различные метрики, включая память JVM, сборку мусора, потоки и другое. Отличный инструмент для тщательного мониторинга производительности JRE.

    MoSKito Monitoring проверяет работоспособность приложения. Имеет открытый код, поддерживает микросервисы, отличный выбор для специалистов DevOps и инженеров SR.

    Nagios — каталог модулей, сценариев и плагинов для приложений и серверов Java.

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

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

    JConsole — инструмент мониторинга, который соответствует спецификации Java Management Extensions (JMX). JConsole использует обширный инструментарий виртуальной машины Java (Java VM) для предоставления информации о производительности и потреблении ресурсов приложениями, работающими на платформе Java.

    Java Flight Recorder и JDK Mission Control — комбинация инструментов для постоянного сбора подробной информации о времени выполнения, что позволяет анализировать инциденты постфактум. Java Flight Recorder — это платформа для профилирования и сбора событий, встроенная в Oracle JDK. С ее помощью можно собирать подробную низкоуровневую информацию о поведении виртуальной машины Java (JVM) и приложения Java. JDK Mission Control — это расширенный набор инструментов, который позволяет эффективно и детально анализировать обширный объем данных, собранных Java Flight Recorder.

    JMap — утилита, которая при использовании jmap с файлом процесса или ядром без каких-либо параметров командной строки печатает список загруженных общих объектов (вывод аналогичен утилите pmap в операционной системе Oracle Solaris). Для получения конкретной информации, можно использовать опции -heap, -histo или -permstat.

    Jps — утилита, полезная в средах со встроенной виртуальной машиной. Позволяет легко распознавать процессы Java в списке процессов.

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

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

    The jstatd Daemon — сервер приложений RMI, который контролирует создание и завершение каждого инструментированного Java HotSpot и предоставляет интерфейс, позволяющий средства удаленного мониторинга для подключения к JVM, выполняющихся на локальном хосте.

    Visualgc — инструмент, который обеспечивает графическое представление системы сборки мусора. Использует встроенные инструменты виртуальной машины Java HotSpot.

    Pinpoint — это инструмент APM (Application Performance Management, Управление производительностью приложений) для крупномасштабных распределенных систем, написанный на Java, PHP или Python. Pinpoint предоставляет решение, которое помогает проанализировать общую структуру системы и то, как компоненты в них взаимосвязаны благодаря отслеживанию транзакций в распределенных приложениях.

    Scouter — еще один инструмент APM (управление производительностью приложений) с открытым исходным кодом.

    Sematext Experience — инструмент мониторинга, который позволяет отслеживать производительность приложения и получать предупреждения всякий раз, когда он обнаруживает аномалии, влияющие на работу конечного пользователя.

    Sematext Logs — служба ведения журналов, которая позволяет централизовать журналы из различных источников, таких как приложения, микросервисы, операционные системы, различные устройства и, конечно же, приложения на основе Java.

    Sematext Cloud — это платформа наблюдения с широким выбором функций. Благодаря простой и удобной настройке, мониторинг Java-приложения выполняется легко и быстро. С предварительно созданными готовыми панелями мониторинга можно начать мониторинг приложения на основе JVM сразу после завершения установки агента. Sematext Logs также позволяет анализировать журналы с помощью функции автоматического обнаружения журналов для приложений Java или отправлять их через одного из поддерживаемых поставщиков журналов.

    Site24x7 — инструмент, с помощью которого разработчики могут легко выявить медленные внутренние вызовы (методы) в коде Java и просмотреть весь путь в виде дерева. Трассировка будет отображать последовательность вызовов URL, включая определенные пользователем методы.

    Java Solar Winds — инструменты, которые помогают улучшить и оптимизировать производительность Java-приложений.

    Stackify — набор инструментов для мониторинга производительности Java-приложений.

    Stage Monitor — решение с открытым исходным кодом, чтобы мониторить производительность серверных приложений Java.

    VisualVM — визуальный инструмент с поддержкой Java 16 и Apple M1, объединяющий инструменты командной строки JDK и облегченные возможности профилирования. Предназначен как для разработки, так и для использования во время производства.

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

    YourKit Java Profiler — инструмент профилирования Java-приложений как на стадии разработки, так и на стадии производства.


    Каждый год на JPoint эксперты выступают с хардкорными докладами о производительности Java. И ни разу не было скучно — вопрос сохраняет актуальность на протяжении многих лет. О том, откуда растут ноги у мифов, что делает JVM, как измерять производительность, при чём тут бизнес-требования заказчика и как обойти часть граблей мы поговорили с экспертами, для которых Java performance — не проблема, а работа.

    Java Performance и всемогущая JVM


    Что происходит на стороне Java Virtual Machine (JVM), как она влияет на производительность? Об этом и немного о Java performance в целом мы поговорили с JVM Engineer в SAP Фолькером Симонисом (Volker Simonis). Фолькер отвечал на вопросы по-английски, здесь мы публикуем перевод.

    — Почему говорят, что Java медленная и проблемы производительности критичны?

    — Я думаю, что восприятие Java как чего-то медлительного осталось в прошлом. В настоящее время виртуальные машины Java имеют реально навороченные JIT-компиляторы и алгоритмы сборки мусора, которые обеспечивают очень хорошую производительность для обычных приложений. Конечно, вы всегда можете найти примеры, где Java медленнее, чем нативные C/C++ приложения. Но такие системы, как Hadoop, Neo4j или H20 — примеры того, что даже большие, сложные высоконагруженные приложения могут быть написаны на Java. То есть производительность Java очень высока и как технология Java — весьма конкурентоспособна.

    По моему опыту люди сегодня больше жалуются на производительность «альтернативных» языков для JVM, таких как Ruby (JRuby), Clojure или Scala. Я думаю, что JVM сможет оптимизировать их так же хорошо, как и чистый Java-код. Это лишь вопрос времени.

    — Как оценивать и измерять производительность Java?

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

    И пока JMH, в основном, отвечает за измерение и оптимизацию производительности Java-кода, продолжает существовать проблема улучшения производительности GC. И это уже другая история, которая требует свой, другой набор инструментов и экспертизу.

    — Где чаще всего бывают проблемы с производительностью?

    — Это, конечно, в значительной степени зависит от типа вашего Java-приложения. Если речь о приложениях, привязанным к данным (то есть, если исполняется простой код на огромных данных), вероятно, более важно правильно выбрать и правильно настроить ваш GC-алгоритм. И даже в этом случае вам сперва придётся выбрать, хотите вы оптимизировать приложение для максимальной пропускной способности или же вы считаете более важным latency приложения.

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

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

    — Вы — эксперт по JVM. Как часто JVM становится причиной низкой производительности? В чём основные проблемы и как с ними бороться?

    — В идеале, после определённого количества итераций Java-приложение должно достичь состояния, при котором по меньшей мере 95% времени работает скомпилированный JIT-ом код. При этом происходят регулярные, но довольно короткие GC-паузы. Это можно легко проверить с помощью таких инструментов, как JConsole, JITwatch или Flight Recorder. Если это не так, то нужно выяснить, какая часть виртуальной машины вызывает проблему и затюнить (или заоптимизировать) соответствующий компонент VM.

    Вы, может быть, знаете, что продуктовая версия HotSpot JVM предлагает около 600 так называемых расширенных опций (это те опции, которые начинаются с -ХХ), а debug-сборка включает более 1200 таких опций (чтобы получить полный список, вы можете использовать ключ -XX:+PrintFlagsFinal). С помощью этих опций вы можете получить детальную информацию о том, что происходит внутри виртуальной машины. Но самое главное, то что эти опции могут использоваться для тонкой настройки практически любой подсистемы виртуальной машины. Плохая новость заключается в том, что эти опции не очень хорошо документированы, так что, вероятно, вам придётся заглянуть в исходники, чтобы понять, что происходит на самом деле.

    Некоторые проблемы со сборкой мусора могут решиться путём выбора правильного Garbage Collector (в текущей версии HotSpot их там полно) и правильной конфигурации heap-а. Проблемы производительности в сгенерированном коде часто вызываются неправильным выбором того, что нужно инлайнить. Опять-таки, это влияет в определённой степени, но, увы, это очень сложная тема и трудно добиться улучшения в одной части кода без снижения производительности других частей.

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

    Наконец, я хочу отметить, что в последнее время мы всё чаще видим проблемы с работой Java в виртуальных средах. JVM совершает большое количество «самоконфигураций» при запуске, чтобы оптимально адаптироваться к окружающей среде. Но вот если информация о реальных ресурсах (таких как количество CPU или объём доступной памяти), не соответствуют тому, что получила JVM, это может привести к очень плохой производительности.

    Например, если операционная система рапортует о большом количестве доступных логических CPU, виртуальная машина создаст множество потоков для сборки мусора и для JIT-а. Но если хостовая операционная система будет распределять эти потоки на совсем небольшое количество физических CPU, то пользовательские потоки Java-приложения могут быть полностью вытеснены системными потоками виртуальной машины. Схожая проблема возникает, если гостевая операционная система выделяет для какой-то JVM большой объем памяти, но эта память шарится (делится) между другими гостевыми ОС.

    — Как сейчас реализуются алгоритмы управления памятью в JVM, что делается для того, чтобы разработчики получали меньше проблем с производительностью?

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

    Однако JVM работает еще со множеством других пулов памяти. Существует metaspace, который хранит загруженные классы и кеш для хранения сгенерированного виртуальной машиной кода (самый, наверное, известный вид кода – это скомпилированные JIT-ом методы, а также сгенерированные куски интерпретатора и различные заглушки кода).
    Ну и наконец, различные подсистемы виртуальной машины, такие как GC или JIT-компилятор, могут на некоторое время потребовать существенную часть нативной памяти для того, чтобы выполнить свою работу. Эта память обычно выделяется на лету и поддерживается в различных сегментах, ресурсных зонах или кэше подсистемы. Например, JIT-компиляция большого метода может временно потребовать больше гигабайта нативной памяти. Все эти пулы памяти могут быть настроены под требования конкретного приложения.

    — Расскажите, пожалуйста, интересную историю из вашей практики, когда была проблема производительности Java. Как она была решена?

    — Наша SAP JVM работает на множестве различных платформ, среди которых есть HP-UX на PARISC и Itanium CPU. HP-UX на Itanium обеспечивает программную эмуляцию для бинарных файлов PARISC, что делает возможным легко и прозрачно запускать приложения PARISC. Правда, на порядок медленнее, чем нативные приложения.

    После того, как мы получили слишком много жалоб клиентов на очень плохую производительность нашей JVM на HP-UX/Itanium, мы стали разбираться, в чем дело. Оказалось, что клиенты использовали бинарные файлы PARISC для запуске на HP-UX/Itanium. Поскольку объяснять каждому клиенту сложности эмуляции мы не могли, мы просто добавили в очередную версию нашей JVM проверку, которая запрещает исполнение кода для PARISC в режиме программной эмуляции.

    Java Performance для Enterprise


    Согласитесь, неприятный момент, когда приложение вышло в продакшен и вернулось с возмущёнными замечаниями клиента по поводу торможения и зависания. Проблемы производительности Java критичны, прежде всего, для Enterprise решений. Именно поэтому за хардкорными практическими советами мы отправились к эксперту, Performance Architect в NetCracker (telecom solutions company), Владимиру Ситникову.

    «О возможных проблемах производительности Java приходится слышать как в учебной, так и профессиональной среде. В каждой шутке есть доля шутки. Отчасти медлительность Java — это миф, тем более, что производительность платформы меняется со временем и то, что вызывало критику лет 10 назад, сейчас работает совершенно по-другому. Рассуждать о Java performance на уровне «тормозит — не тормозит» опрометчиво.

    На практике, основные проблемы в промышленных приложениях связаны с наличием лишнего кода, когда действий выполняется больше, чем нужно для решения задачи. Где-то разработчик перемудрил, а где-то бизнес-требования так запутаны, что с первого раза быстро не напишешь. Иногда производительности железа хватает, а иногда решение работает медленнее, чем ожидалось. Основная рекомендация здесь одна — не выполнять ненужный код или хотя бы его часть.

    Ещё одна причина снижения производительности кроется в алгоритмах; когда, например, используют полный перебор вместо того, чтобы найти запись по алгоритмическому признаку. Но на практике хитроумные (более умные, чем HashMap) структуры данных редко используются для повышения производительности.

    Могут оказывать влияние на производительность и сторонние инструменты разработки, например, библиотеки и фреймворки. На практике проблемы могут быть на всех уровнях — и они случаются. Разработчики обычно выявляют их на тестах, проводится анализ с помощью разных техник профилирования (на уровне браузера, сервера приложений, базы данных). Проблемы со сторонним кодом возникают часто, и единственный способ отловить их — делать замеры и тщательно следить за развитием этого самого стороннего кода. В моей практике, что ни месяц, то находка. Бывает, замена одной строки кода ускоряет вставку данных в базу в 10 раз.

    Вообще, главное в измерении и работе с производительностью Java — не забывать о нагрузочном тестировании. Соответственно, сколь велики и разлаписты бизнес-требования к enterprise-разработке, столь огромны тесты, которые их проверяют и помогают заметить неприятные моменты, связанные с performance. Например, нужно учитывать, что нагрузка в корпоративной среде неравномерная: утром все пришли, вошли в систему, пик нагрузки, затем к обеду идёт спад, в обед нагрузка наименьшая, а после обеда пользователи с новой силой повышают нагрузку до высоких значений. Конечно, при первом анализе такие нюансы могут быть упущены. А по факту они могут очень сильно сказываться на том, где ожидать массовой нагрузки и как писать код, чтобы избежать неприятностей с производительностью.

    Вы вот меня спрашиваете про самые типичные ошибки и грабли. У каждого они свои. Например, выбор конкретной модели процессора или памяти редко что меняет. Конечно, если сравниваются образцы 2006 и 2016 годов, то модель имеет значение. Много вопросов лежит в плоскости масштабируемости. Многие находятся в поисках священного Грааля enterprise-разработки: все хотят, сделав единственный замер, предсказать, сколько нужно серверов и какой мощности для разворачивания приложения. Тут единственный выход — тестировать на живом сервере, учитывать запасы ресурсов для отказоустойчивости.

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

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

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

    Кстати, о жалобах. Если клиент негодует, чтобы понять, где искать проблему, есть простой алгоритм. Нужно спросить представителя заказчика, а как должно быть. Если всё отрабатывает медленно и вам кажется, что дело в субъективных ощущениях, уточните, за какое время должно работать. Такие требования, конечно же, лучше собирать тоже заранее (во время разработки, или до её начала). Мы требования разбиваем по компонентам: например, если пользователь хочет, чтобы страница открывалась за 5 с, мы распределяем бюджет на браузер — 1 с, на серверную часть — 3,5 с, сеть — 0,5 с и так далее. Зачастую разбивка идёт вплоть до компонентов самой системы, которые, кстати, логируют время работы, и разработчики, открыв профайлер, могут проверить укладывается ли их код в допустимые рамки.

    Итак, проблемы есть. Куда смотреть, откуда ноги растут?

    • Посмотреть на загруженность CPU и содержимое GC логов. Если система загружена, и при этом нагрузка вызвана работой GC, то либо памяти мало (нужно повышать -Xmx), либо «кто-то слишком много ест».
    • Сама по себе 50-100% загруженность уже говорит о том, что времена отклика могут получаться самыми невообразимыми.
    • Бывает, вина лежит на сторонних приложениях. Безобидное обновление может привезти пачку тормозов «в подарок». Канонический пример — история с RedHat Linux: механизм transparent huge pages вызывает проблемы с производительностью. Это «фича» Red Hat Linux, из-за которой «внезапно» начинают тормозить как java-приложения, так и базы данных. Проблеме подвержены Red Hat Linux 6+ (CentOS 6+). По ссылкеможно почитать подробнее. THP режим нужно обязательно отключать. Если подобная настройка железа и программ выполняется вручную, то не исключён человеческий фактор, и в итоге — тормоза.
    • Задавать классические вопросы «работало ли оно раньше» и «что меняли». Спрашивать, что делали на стороне клиента, накатывали ли обновления на компоненты системы. Если накатывали — вектор поиска становится понятнее. Если ясности нет, память свободная, тогда смотрим в профайлер, какая активность занимает ресурсы. Ну а дальше у каждого своя история со своим кодом.

    Теперь посмотрим на проблему производительности поближе к реальным условиям, к клиенту. Нередко приложение совершенно неожиданно ведёт себя в продакшене, хотя на тестах всё было идеально. И этому есть объяснение. Если ситуация воспроизводится только у клиента, то это может говорить о проблеме с исходными данными. Например, мы знаем, что приложению предстоит совершать операции с 1’000’000 клиентами. Мы генерируем 1’000’000 случайных имён и фамилий, тесты проходят хорошо, поиск клиента по ФИО летает. Выходим в продакшн — пользователи негодуют. Смотрим внимательнее на проблемный поиск — и оказывается, что Ивановых Иванов Ивановичей тысяча. Если в нагрузочных тестах не учесть такое количество повторений, то немудрено, что на тестах пара найденных строк отображаются мгновенно, а в эксплуатации поиск работает через раз.

    Сгенерировать корректный набор данных для тестов — целая наука. Нужно создать не просто осмысленные, допустим, 100 ГБ данных, но и учесть взаимодействия между данными — должно быть максимально похоже на то, что будет в эксплуатации.

    • Идеальный случай — наличие дампа базы клиента. Его можно использовать напрямую или предварительно зашифровать. Причём шифрование должно учитывать существующие соотношения и ссылки между объектами и обеспечивать безопасность данных одновременно. Например, простая замена всех значений на MD5, не является достаточно безопасной. Даже поиск в Google по MD5 может найти «исходное» значение. Правильнее использовать HMAC, чтобы с одной стороны одинаковые данные хешировались в одинаковые хэши, но при этом дешифрация будет невозможна.
    • Бывает и так, что дампа нет. В таком случае изучаются параметры будущей системы и пишутся скрипты, которые генерируют тестовые данные на основе этих параметров. При этом стоит избегать равномерных распределений, ведь, в реальности редко когда распределения равномерны.

    Что ещё можно сказать о Java performance для enterprise? Собирайте нефункциональные требования заказчика: разбивку по сценариям работы, частоту взаимодействий пользователя с тем или иным модулем приложения, количество пользователей и ожидаемое время работы. Исправление проблем производительности на поздних этапах может стоить очень дорого, а лишняя секунда пока ждёт оператор колл-центра приводит к серьёзным потерям».

    Если у вас возникли вопросы по высказываниям экспертов или вы хотите рассказать о своей позиции по вопросам производительности Java, добро пожаловать в комментарии на конференцию JPoint 2016 22-23 апреля. Все там будем!

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