JVM Memory Profiling

Async Profiler

最近一直忙着搞AI 的application adoption, 都没空记录点东西。 趁着最近有个memory的profiling,记录一下如何进行memory profiling.

对于大部分大厂,应该有成熟的监控工具,比如dynatrace,消费级产品使用起来很方便。这里不详细介绍。

Dynatrace有个很方便的火焰图,对于本地我也想要这个效果,那么你可以使用 Async Profiler。

Dynatrace 火焰图示例

安装

这里安装 Async Profiler

这里是文档

Quick Start(本地最常用三连):

asprof -d 30 -e cpu -o flamegraph -f cpu.html <PID>
asprof -d 30 -e alloc --total -o flamegraph -f alloc.html <PID>
asprof -d 30 -e alloc --live -o flamegraph -f heap.html <PID>

CPU 看热点;alloc 看高频瞬时分配;alloc --live 看仍存活对象来源(等价于之前的 -e heap)。

基本操作

asprof 提供了 start, stop, dump, status 等基本操作,可以更灵活地控制采样过程。-d 参数只是 start -> sleep -> stop 的一个快捷方式。

  • start: 启动采样。分析器会持续在后台运行。
  • stop: 停止采样并输出报告。
  • dump: 在不停止采样的情况下,生成一份当前已采集数据的快照。
  • status: 查看当前采样状态。

例如,你可以手动开始,在需要的时候dump,最后再停止:

# 启动CPU采样
asprof start -e cpu <PID>

# ... 执行一些操作后 ...

# dump出一份火焰图,但采样并未停止
asprof dump -o flamegraph -f cpu-dump-1.html <PID>

# ... 再次执行一些操作 ...

# 停止采样并生成最终报告
asprof stop -o flamegraph -f cpu-final.html <PID>

JFR + JMC

Quick Start

Use VisualVM 创建一个JFR,或者你也可以用命令行,如下图

创建JFR

在JMC里查看

查看Flame Graph

JFR+JMC功能很强大,可以进行很多topic的profiling,比如cpu,锁。

为什么用 JFR

  • 低开销(生产可常驻,默认 <1%)
  • 事件维度丰富:GC、线程、锁、IO、类加载、编译、内存、CPU 样本
  • 时间线视图 + 事件过滤,适合定位“某一时刻发生了什么”

启动方式

命令行临时采样 (60s):

jcmd <PID> JFR.start name=diag settings=profile duration=60s filename=app.jfr

持续后台:

jcmd <PID> JFR.start name=bg settings=default disk=true maxage=30m maxsize=256m filename=rolling.jfr

停止:

jcmd <PID> JFR.stop name=bg

常用 settings

  • default: 低开销,适合长期
  • profile: 更多采样 (ExecutionSample, AllocationInNewTLAB 等)
  • 自定义: 基于 JMC 导出 .jfc 修改事件频率后再加载

关键事件关注

场景 事件
CPU 热点 ExecutionSample, NativeMethodSample
GC 频繁 GCHeapSummary, GCPhasePause, GCPromotionFailed
锁竞争 JavaMonitorEnter, ThreadPark, SynchronizerStatistics
内存膨胀 ObjectCountAfterGC, AllocationInNewTLAB, AllocationOutsideTLAB
类加载慢 ClassLoad, ClassDefine
JIT 延迟 Compilation, CodeCacheConfiguration

典型分析工作流

  1. 发现延迟抖动 → 看 GCPhasePause 是否与高延迟对齐
  2. 线程阻塞多 → 检查 JavaMonitorEnter / ThreadPark 的堆栈
  3. CPU 飙升 → ExecutionSample → 展开最宽栈帧 → 对应业务热点
  4. OOM 前兆 → AllocationOutsideTLAB + ObjectCountAfterGC 变化趋势
  5. 启动慢 → ClassLoad + Compilation 时间线

与 Async Profiler 联合

  • 先用 JFR 获取宏观事件时间线 (GC/锁/线程状态)
  • 再用 async-profiler 针对其中一段时间做精细火焰图(CPU / alloc / live)
  • 可用 asprof --jfrsync profile -f combined.jfr <PID> 直接生成含采样的 JFR
  • 对比:JFR 的 ExecutionSample 频率低;async-profiler 更精准栈 + 非安全点采样

常见问题

问题 处理
文件过大 使用 maxage + maxsize 滚动
ExecutionSample 缺失业务栈 -XX:+DebugNonSafepoints
锁事件少 真实没竞争或 settings 过滤,换 profile / 调高 period
无分配事件 默认 settings 不含分配,改用 profile 或自定义开启 Allocation*

自定义 .jfc 要点

  • 降低频率:只保留需要的 10~20 个事件
  • 关闭不看的网络/文件事件以减小体积
  • 增 AllocationInNewTLAB / OutsideTLAB 时注意开销

分析技巧

  • 时间线对齐:选中高延迟点 → “Show Related Events”
  • 线程过滤:锁问题先按“Blocked Time”排序
  • 频繁小 GC:看 TLAB 分配速率是否异常
  • 代码缓存满:关注 CodeCacheFull / Compilation 完成率

何时不用 JFR

  • 只需一次性的极短 CPU 火焰图 → 直接 async-profiler
  • 容器极度受限无法写入文件 → 退化为简单采样
  • 超短压测 (<10s) → JFR 事件密度低,优先采样器

快速决策表

目标 优选
持续运行监控 JFR default
细粒度热点 async-profiler cpu
瞬时分配压力 async-profiler alloc --total
内存泄漏来源 async-profiler alloc --live + JFR ObjectCountAfterGC
线程阻塞 JFR JavaMonitorEnter + ThreadPark
GC 停顿归因 JFR GCPhasePause + Safepoint (若启用)

输出管理

  • 定期打包: cron 收集 *.jfr → 压缩归档
  • 安全: 包含类名/路径,需内部环境使用
  • 解析: JMC GUI 或 jfr print --events <event> file.jfr

收尾

采样前后做基线对比;事件与火焰图交叉验证,避免单点误判;关注趋势而非单次尖峰。

posted @ 2025-11-24 14:22  开学五年级了  阅读(10)  评论(0)    收藏  举报