性能指标
基本
- 吞吐量
- 延迟
监控系统指标:
- CPU使用率,load, (Docker) Throttled_time,用户时间,内核时间
- 内存使用率(java进程Rss),swap
- 网络IO (连接数,连接状态,吞吐量)
- 磁盘IO
监控jvm:
- 堆内存 (总占用,Eden占用,Survivor占用,OldGen占用)
- 垃圾回收 (次数,延迟,Full GC)
- jit (总编译时间,CodeCache 使用)
监控外部依赖
- 请求量
- 延迟
- 错误数
性能分析
简单的排队模型理论: 延迟随使用率呈指数上升趋势
明确性能优化目标,延迟 or 吞吐
建立性能测试环境!!!与生产越接近越好
吞吐量:不断增大负载,检查性能指标,直到出现瓶颈
延迟:各个代码模块,关键路径自行埋点,并形成监控面板
自顶而下的进行性能分析:应用->JVM->操作系统->硬件->外部依赖
找出
- 过渡消耗资源的代码
- 未充分使用资源的代码
性能分析工具基本原理
- 采样 (Sampling)
- 插桩 (instrumentation)(字节码增强)
CPU:
- 观察指标:top, vmstat, sar -u 1
- 采样分析:arthas,async-profiler cpu
CPU使用率高
可能性1:系统负载过高,检查请求数,数据规模,准备扩容
可能性2:代码bug,死循环,特征为拉出集群后,CPU使用率仍保持恒定值
排查思路:
1.top -Hp {pid} 查看进程下各线程,找出CPU占用率高的线程ID
2.jstack 查看对应线程的堆栈,定位代码
或者使用 arthas 命令 thread -n 5 (前最忙的前N个线程并打印堆栈)
可能性3:低效热点代码,使用arthas 或 async-profiler 对cpu进行采样绘制火焰图,定位代码位置
内存:
- 观察指标:top, pmap, ps, NMT, sar -r
- 采样分析:arthas,async-profiler alloc,jemalloc dump
堆外(java进程RSS)内存上升
通过NMT查看,是JVM自带的本地内存追踪工具,能够分析JVM自身内部分配的一些内存,但是无法追踪非JVM分配的内存,例如JNI等native code。
很多NIO框架,序列化,GZIP会在堆外内存分配缓冲区,使用ByteBuffer分配,或者JNI分配
原生内存分配器,替换ptmalloc 为 jemalloc 或 tcmalloc,配合使用其内部的内存分配采样功能
堆内存:
- 观察指标:jmx, jmap, jstat
- 分析dump:MAT,jprofiler
堆内内存上升
堆内内存泄漏,jmap 抓dump,下载到本地使用MAT或者Jporfiler分析,重启抓一次,内存上升后再抓一次,比较两个dump中的不同,查找泄漏对象的GC root,确定泄漏代码位置
垃圾收集:
- 观察指标:jmx, jstat -gc
- 分析:GC日志,可用GCViewer 或者 gceasy在线分析
GC频繁,吞吐率低
可能为内存分配速率过快,或者堆内存容量过小
使用工具分析GC日志,查看分配速率
使用jmap -histo 查看对象直方图,检查分配较多的对象
使用arthas 或 async-profiler 对内存分配进行采样绘制火焰图,查看内存分配热点
优化应用,或者扩容堆内存
GC时间长
检查GC日志,找出耗时的阶段
以G1日志为例:
- [Update RS (ms): 更新RSet耗时,如果过长,说明可能存在大量的跨region引用并且频繁更新。检查是否有频繁变化的对象图结构
- [Object Copy 存活对象复制耗时,如果过长,说明可能存在大量存活对象,可能是年轻代过小,回收间隔太短导致对象来不及死亡
并发标记频繁触发
不断的并发标记导致占用大量CPU时间
可能:IHOP阈值过小,年轻代对象晋升过快,过多大对象分配
Full GC
分析GC日志,结合当前使用的回收器,具体情况具体分析
CMS,G1 这些并发回收器出现Full GC必然是异常的,常见原因:CMS,G1 并发模式失效。晋升空间不足,转移失败
其他回收器的需要考虑full gc触发的频率以及停顿时间的长短
网络:
- 观察指标:netstat
通过状态判断是在TCP什么环节出了问题
连接泄漏
大量CLOSE_WAIT,查看对端ip与端口,定位代码位置
case:https://nothinghappen.github.io/2020/11/17/TCP%E8%BF%9E%E6%8E%A5%E5%A4%A7%E9%87%8FCLOSE_WAIT.html
延迟:
- 自行埋点监控,形成监控面板
- 详细的单次查询性能trace日志,打印单次查询调用链上所有处理节点的耗时
- arthas monitor ,通过字节码增强,注入方法级别的埋点切面
性能优化:
针对找出的瓶颈,进行优化
顾全大局,先优化大头
不断的实验并验收优化效果
最好的性能来自于充分利用现有资源,消灭不必要的工作
应用调优
延迟优化
- 并行化
- 缓存
- 优化算法时间复杂度/降低数据规模
(分布式缓存)序列化优化
- 数据动静分离,静态数据放入本地内存缓存
- 避免同一引用的对象重复序列化
- 数据精简(冗余字段去除,字符串编码)
内存优化
- 本地内存缓存瘦身(字符串重用,字符串编码,驱逐策略)
- 堆外缓存 + 压缩 (绕过GC,通过压缩CPU换内存)
- 字符串重用 String intern ,G1 字符串去重
jvm调优
- 垃圾回收
- jit
垃圾回收
衡量垃圾回收的指标
- 吞吐
- 停顿时间
确定垃圾回收是否是性能瓶颈
- 大量CPU时间消耗在GC相关工作上
- GC停顿时间占比过高
准确了解自己应用的内存特点,确定优化思路。
考虑:
- 分配速率
- 短生命周期,中年,长生命周期对象各有多少
引擎的内存特点:
- 大量分配短生命周期对象
- 中量大对象(序列化反序列化)
- 少量长生命周期对象(本地缓存)
- 基本没有中年对象
大量分配短生命周期对象意味着每次GC的存活对象很少,GC开销在于每次GC的存活对象数量。每次GC开销一定的情况下,尽量减少Young GC次数
优化基本思路:尽量扩大年轻代
面临问题:
- 大的年轻代空间挤压老年代和大对象空间
- 大对象空间挤压老年代空间
- 本地缓存集中刷新导致突然存在大量存活对象晋升至老年代,导致转移失败,或者并发模式失效
优化方案:
- 缓存刷新错峰与降频,canal + mq 实时刷新
- 堆外内存缓存(绕过GC机制)
- 精简本地缓存
- 扩大G1 Region Size,减少大对象数量
jit
使用jitwatch分析查看编译日志
分层编译,C1编译,C2编译
调整编译模式(client,server,分层编译)
调整CodeCache空间大小,遇到过CodeCache空间耗尽导致编译线程大量消耗CPU的bug
https://blog.csdn.net/weixin_38405253/article/details/102675225