工作总结_性能优化(粗稿)

性能指标

基本

  • 吞吐量
  • 延迟

监控系统指标:

  • 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