惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Engineering at Meta
Engineering at Meta
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
博客园 - 三生石上(FineUI控件)
量子位
腾讯CDC
The Cloudflare Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
云风的 BLOG
云风的 BLOG
Vercel News
Vercel News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
L
LangChain Blog
aimingoo的专栏
aimingoo的专栏
The Hacker News
The Hacker News
T
The Exploit Database - CXSecurity.com
B
Blog
S
SegmentFault 最新的问题
P
Privacy & Cybersecurity Law Blog
T
Threatpost
博客园 - 聂微东
T
Tailwind CSS Blog
The Last Watchdog
The Last Watchdog
C
Check Point Blog
N
Netflix TechBlog - Medium
D
DataBreaches.Net
爱范儿
爱范儿
IT之家
IT之家
S
Secure Thoughts
M
MIT News - Artificial intelligence
NISL@THU
NISL@THU
C
Cisco Blogs
TaoSecurity Blog
TaoSecurity Blog
有赞技术团队
有赞技术团队
A
Arctic Wolf
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
P
Proofpoint News Feed
Spread Privacy
Spread Privacy
Schneier on Security
Schneier on Security
Simon Willison's Weblog
Simon Willison's Weblog
G
GRAHAM CLULEY
雷峰网
雷峰网
Project Zero
Project Zero
博客园 - Franky
H
Heimdal Security Blog
A
About on SuperTechFans
Security Latest
Security Latest
Webroot Blog
Webroot Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Hugging Face - Blog
Hugging Face - Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
async‑profiler в production: CPU, аллокации, lock contention и чтение flame graph
artem · 2026-05-29 · via Все публикации подряд на Хабре

Средний

10 мин

6.5K

Стандартный набор инструментов для профилирования Java‑приложений из коробки JDK — JFR (Java Flight Recorder), jstack, jmap, VisualVM — закрывает многие задачи, но в реальных же сценариях у каждого есть ограничения.

JFR хорош для общего обзора, но плохо показывает горячие методы из‑за безопасных точек (safepoint bias) — sampling срабатывает только на safepoint, и методы, инлайненные JIT или без safepoint‑poll, в выборку не попадают. jstack даёт срез стека на момент вызова, но один срез — это случайность, а серия с шагом 100 мс уже неудобно анализировать руками. VisualVM требует JMX‑подключения, что в проде с сетевой политикой или Kubernetes‑подами часто блокировано.

async‑profiler — внешний инструмент, который в целом решает эту проблему. К маю 2026 актуальная версия — async‑profiler 4.3, команда перешла на квартальный цикл релизов. С январских обновлений 2026 года инструмент идёт в комплекте Amazon Corretto, отдельно скачивать не нужно.

В статье обзор основных режимов профилирования, как использовать, как читать flame graph и где async‑profiler помогает, а где лучше взять другой инструмент.

Установка и базовые команды

Скачать архив:

wget https://github.com/async-profiler/async-profiler/releases/download/v4.3/async-profiler-4.3-linux-x64.tar.gz
tar xzf async-profiler-4.3-linux-x64.tar.gz
cd async-profiler-4.3-linux-x64

Бинарник называется asprof (в версиях до 3.x он был profiler.sh). На Amazon Corretto бинарник доступен в стандартной поставке JDK. Поддерживаются Linux и macOS, JDK 8 и новее, на Linux одинаковые бинарники работают и с glibc, и с musl.

Системные требования. На Linux нужны два sysctl‑параметра, иначе async‑profiler не сможет собирать стеки:

sudo sysctl kernel.perf_event_paranoid=1
sudo sysctl kernel.kptr_restrict=0

Без них вы получите ошибку perf_event_open failed: Permission denied или нулевые сэмплы. В Docker‑контейнерах это иногда требует --cap-add SYS_ADMIN или запуска с --privileged. В Kubernetes — добавления capabilities в SecurityContext пода, что обычно требует согласования с инфраструктурной командой.

Базовая команда — запустить профилирование на 60 секунд и сохранить flame graph:

asprof -d 60 -f flamegraph.html <PID>

PID Java‑процесса можно посмотреть через jps -l (там видны процессы и main‑классы). Результат — самодостаточный HTML‑файл, который можно открыть в браузере. Внутри — интерактивный SVG, в котором можно кликать по блокам и спускаться в детали, или искать по тексту метода.

Для коротких операций (несколько секунд) полезна опция -e cpu, чтобы явно задать event; по умолчанию это и так CPU, но при копировании команд явность экономит время.

CPU profiling и чтение flame graph

Команда для CPU‑профилирования:

asprof -e cpu -d 60 -f cpu.html <PID>

Каждый блок — это метод, ширина блока пропорциональна времени, которое CPU провёл в этом методе (включая вызываемые из него методы). По вертикали — стек вызовов: main‑методы внизу, более глубокие — наверху. Цвет блоков отражает категорию: зелёный — Java‑методы, жёлтый — C++ внутри JVM, красный — нативные функции ядра и системные библиотеки.

Главные паттерны, на которые смотреть:

  • Широкая плоская «вершина» — главная горячая точка, где CPU проводит большую часть времени. Обычно туда уходит первая‑вторая инвестиция в оптимизацию. Если плато даёт 30–40% общего времени, замена этого метода на более эффективный алгоритм или кэш даст пропорциональный выигрыш.

  • Узкие глубокие «пики» — много времени уходит в один глубокий стек. Часто это указывает на рекурсивный код, который делает что‑то сложное.

  • Equal‑width блоки сверху — JIT не справился оптимизировать, метод существует на одном уровне без inlining. В Java это часто полиморфные вызовы, которые JIT не смог девиртуализировать (megamorphic call site), или методы с большим bytecode size, превышающие лимиты MaxInlineSize.

  • Много мелких stacks — общая нагрузка распределена, нет единой горячей точки. Это значит, что простой подход «оптимизировать один метод» не сработает, нужна архитектурная переработка.

Один частый паттерн в Spring Boot‑сервисах — большая часть CPU уходит в Jackson serialization, JPA query building через Hibernate, или Spring AOP‑прокси. Если на flame graph видно, что 30% CPU тратится в com.fasterxml.jackson.databind.ser.std.BeanSerializerBase, имеет смысл посмотреть на размер JSON‑ответов, подумать о projection DTOs вместо полных Entity, или включить кэширование сериализаторов.

Если 20% в org.hibernate.query.sqm — это сборка SQL‑запросов из JPQL, и решение либо в HQL вместо Criteria API, либо в native query для горячих путей.

Кстати, по умолчанию async‑profiler вроде как сэмплирует с частотой 1000 Hz (тысяча сэмплов в секунду). Для горячих сервисов этого достаточно, а вот для коротких профилей или редких методов можно поднять до -i 1ms (миллисекундный интервал, 1000 Hz) или опустить до -i 10ms (100 Hz) для снижения накладных расходов.

Allocation profiling: где аллоцируется память

Команда:

asprof -e alloc -d 60 -f alloc.html <PID>

В этом режиме async‑profiler сэмплирует аллокации, выходящие за пределы TLAB (Thread‑Local Allocation Buffer). TLAB — это локальная зона памяти каждого потока, в которую быстро аллоцируются новые объекты без блокировок. Объекты, выходящие за пределы TLAB (большие или редкие), аллоцируются медленнее и попадают в выборку.

На таком flame graph ширина блока пропорциональна объёму аллоцированной памяти, а не времени CPU, и по картинке сразу видно, какие методы создают больше всего объектов в куче и куда уходит давление на garbage collector.

Возможные находки:

  1. Spring AOP‑прокси создают много объектов. Аннотации @Transactional, @Cacheable, @Async оборачиваются в CGLIB или JDK‑прокси, и каждый вызов создаёт массив аргументов для invocation handler. Если метод вызывается миллионы раз и подгребает много объектов, имеет смысл вынести логику в подкласс без аннотаций для горячего пути, или вызывать через явный TransactionTemplate вместо @Transactional.

  2. String concatenation в горячих циклах. В Java 9+ через invokedynamic есть compile‑time оптимизация для оператора +, но если на flame graph видны промежуточные StringBuilder.append, это часто означает, что строка собирается в цикле, и каждая итерация аллоцирует промежуточный массив char[]. Решение — заранее создать StringBuilder нужного размера или использовать String.join для коллекций.

  3. Boxing на горячих путях. Integer.valueOf или Long.valueOf создают объекты‑обёртки. Часто видно в коде, который использует Map<String, Integer> для счётчиков — каждое map.put(key, count + 1) означает auto‑boxing. Решение — LongAdder для счётчиков или примитивные коллекции из Eclipse Collections / fastutil.

  4. Реактивные стримы и WebFlux. На flame graph часто видны MonoFlatMap и FluxConcatMap — это аллокации операторов на каждый запрос. Это нормально для реактивной модели, но если их сильно много и они дают серьёзный GC pressure, имеет смысл подумать о переходе на виртуальные потоки в Spring Boot 4, там тот же блокирующий код пишется без аллокаций реактивных операторов.

  5. Сериализация в logback/log4j2. Если на flame graph много ThrowableProxyConverter или похожих — это значит, что в логах часто пишутся исключения с полным stack trace, что само по себе тяжёлая операция аллокации. Решение — либо не логировать stack trace на warn‑уровне, либо использовать асинхронный appender.

Lock contention: где потоки ждут друг друга

Lock contention — это когда потоки ждут друг друга на synchronized‑блоках или java.util.concurrent локах. Часто не видно через CPU‑профилирование (поток в ожидании не нагружает CPU), но критично для latency: если поток ждёт полсекунды на захвате lock'а, запрос на этой полусекунде висит.

Команда:

asprof -e lock -d 60 -f locks.html <PID>

Дополнительно можно установить порог по длительности (записывать только сэмплы дольше N миллисекунд), чтобы не получать шумные мелкие захваты:

asprof --lock 10ms -d 60 -f locks.html <PID>

С версии 4.3 async‑profiler умеет также сэмплировать pthread mutex contention в native‑коде через --nativelock.

asprof --nativelock 10ms -d 60 -f nativelocks.html <PID>

На lock‑профиле ширина блока — это суммарное время, которое потоки провели в ожидании lock'а, а стек показывает, где случился блокирующий вызов.

Типичные находки:

  • Hot synchronized в общем кэше. Если в коде есть synchronized (cache) и кэш горячий, на flame graph видно широкое плато на этом методе. Лечится переходом на ConcurrentHashMap для простых кэшей или Caffeine для кэшей с eviction.

  • HikariCP до Java 24. Если проект на Java 21–23, lock contention в HikariCP ConcurrentBag бывает очень видна — потоки ждут друг друга на synchronized внутри пула. Лечит обновление на Java 24+ (JEP 491 убрал pinning от synchronized для виртуальных потоков и заодно уменьшил contention в общем случае).

  • Singleton initialization под нагрузкой. Если на flame graph виден Class.<clinit> под synchronized — это значит, что инициализация класса блокирует другие потоки. Часто лечится либо ленивой инициализацией через Initialization-on-demand holder, либо явной инициализацией при старте через Class.forName для классов с тяжёлым <clinit>.

Wall‑clock vs CPU: когда что использовать

CPU‑профилирование показывает, где поток нагружает CPU. Это полезно для CPU‑bound нагрузок, но бесполезно для I/O‑bound, где поток большую часть времени блокируется в ожидании.

Wall‑clock профилирование показывает, где поток находится во времени независимо от состояния (running, waiting, blocked, sleeping):

asprof -e wall -d 60 -f wall.html <PID>

Если ваш сервис делает HTTP‑запрос в стороннюю систему и ждёт ответа 500 мс, в CPU‑профиле этого вызова почти не видно (поток в ожидании, CPU не нагружен). В wall‑clock‑профиле он будет занимать значительную долю.

Когда что использовать:

  • CPU‑bound сервис (расчёты, обработка данных, сериализация, парсинг) — CPU profiling.

  • I/O‑heavy сервис (HTTP‑вызовы, JDBC, Kafka, Redis) — wall‑clock profiling.

  • Сервис с непонятной latency‑проблемой в духе «запрос идёт 2 секунды, не понимаю, куда время уходит» — wall‑clock на одном запросе через -t (per‑thread filtering), чтобы видеть конкретный поток обработки.

Хорошая модель: CPU‑профиль отвечает на вопрос «где жжёт процессор», wall‑clock — на вопрос «где висит время».

Differential flame graphs: сравнение до и после

Можно сравнить два профиля. Снимаете flame graph до изменения, делаете изменение, снимаете после, и async‑profiler показывает разницу: что стало быстрее, что стало медленнее.

Это делается через утилиту jfrconv, которая идёт в комплекте с async‑profiler:

asprof -e cpu -d 60 -f before.jfr <PID>
# делаете оптимизацию, перезапускаете сервис, повторяете нагрузку
asprof -e cpu -d 60 -f after.jfr <PID>

jfrconv --diff before.jfr after.jfr -o diff.html

В получившемся flame graph красные блоки — то, что стало хуже (медленнее или больше аллокаций), зелёные — то, что стало лучше.

Differential работает не только с JFR. async‑profiler 4.x сохраняет данные в JFR‑формате по умолчанию, но jfrconv принимает и flamegraph, что позволяет сравнивать профили из разных запусков.

Подключение к live‑процессу в проде

Принято так, что async‑profiler запускается не при старте JVM, а в момент диагностики — без перезапуска сервиса.

Способы:

  • По PID на хосте. Самый простой: asprof -d 60 -f out.html <PID>. Работает, если бинарь и Java‑процесс на одном хосте и доступ есть с правами того же пользователя.

  • Через docker exec. Если сервис в контейнере, копируем async‑profiler внутрь и запускаем:

docker cp async-profiler-4.3-linux-x64/. <container>:/opt/asprof/
docker exec <container> /opt/asprof/asprof -d 60 -f /tmp/out.html 1
docker cp <container>:/tmp/out.html ./out.html

PID внутри контейнера обычно 1, потому что JVM запускается как entry point.

  • Через kubectl exec для Kubernetes. Аналогично — копируем в под и запускаем. Главное, у пода должен быть Bash или хотя бы /bin/sh, и достаточно capabilities для perf_events. На managed Kubernetes (EKS, GKE) последнее часто требует privileged‑mode или sidecar‑контейнера с правильными настройками.

  • Через JVM‑агент. Async‑profiler можно подгрузить как agent через jcmd без shell‑доступа:

jcmd <pid> JVMTI.agent_load /path/to/libasyncProfiler.so start,event=cpu,file=/tmp/out.jfr
# через минуту:
jcmd <pid> JVMTI.agent_load /path/to/libasyncProfiler.so stop,file=/tmp/out.jfr
  • Через AP‑agent (отдельный проект dpsoft/ap-agent) — мини REST API для запуска профилирования по HTTP. Удобно для встроенных диагностических эндпоинтов:

java -javaagent:/path/to/ap-agent.jar -jar app.jar
# потом:
curl "http://localhost:8080/profiler/profile?event=cpu&duration=60&output=flamegraph" -o flame.html

Главное, что отличает async‑profiler от других профайлеров, — возможность увидеть полную картину: Java‑методы, нативный код, системные вызовы на одном графе. Когда у сервиса latency непонятного происхождения, async‑profiler даёт первый ответ за минуту: где висит время, какие методы горячие, есть ли lock contention. Дальше уже копать конкретные слои — JDBC, HTTP‑клиенты, GC, JIT.

Если тема производительности Java‑приложений вам близка, присмотритесь к курсу «Java разработчик. Продвинутый уровень». На курсе рассмотрим инструменты и подходы, которые нужны Java‑разработчику для работы со сложными production‑системами: многопоточность, производительность, современные возможности JVM и архитектурные решения для высоконагруженных приложений.

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

Также приходите на бесплатный открытый урок:

На уроке поговорим о виртуальных потоках в Java 21: как они упрощают разработку высоконагруженных приложений, помогают писать масштабируемый код и снижают сложность управления потоками. Это хорошая возможность познакомиться с преподавателем‑практиком, протестировать формат обучения и задать вопросы по теме.