一、概述
1、生产环境中的问题
- 生产环境发生了内存溢出应该如何处理
- 生产环境应该给服务器分配多少内存合适
- 如何对垃圾回收器的性能进行调优
- 生产环境CPU负载飙高应该如何处理
- 生产环境应该给应用分配多少线程合适
- 无log,如何确定请求是否执行了某行代码
- 无log,如何实时查看某个方法的入参和返回值
2、调优基本问题
- 为什么要调优
- 防止OOM出现,对JVM规划和预调优
- 解决程序运行中各种OOM
- 减少full GC的出现频率,决绝运行慢、卡顿的文图
- 调优的方向
- 合理的编写代码
- 充分并合理的使用硬件资源
- 合理的进行JVM调优
- 不同阶段的考虑问题
- 上线前
- 项目运行阶段
- 线上运行出现OOM
- 原则
- 没有业务场景的调优都是耍流氓
- 无监控、不调优
3、调优监控的依据
- 运行日志
- 异常堆栈
- GC日志
- 线程快照
- 堆转储快照
4、性能优化的步骤
- 属性业务场景
- 性能监控(发现问题)
- GC频繁
- CPU load 过高
- OOM
- 内存泄漏
- 死锁
- 程序响应时间长
- 性能分析(排查问题)
- 打印GC日志,通过工具分析(gceasy)
- 使用命令行工具,jstack、jinfo、jmap等
- 分析dump文件
- 使用图形化工具查看,arthas、jVisualVm、jConsoles等
- 性能调优(解决问题)
- 适当的增加呢次,根据业务场景使用垃圾回收器
- 优化代码,控制内存使用
- 增加节点,分散压力
- 合理设置线程池的参数
- 使用中间件
5、性能评价、测试指标
- 停顿时间
- 吞吐量
- 并发数
- 内存占用
- 相互间关系
二、OOM案例
- 堆溢出
- 元空间溢出
- GC overhead limit execceded:98%的时间都在GC
- 线程溢出
三、性能测试工具:Jmeter
1、主界面
2、使用流程
- 新增线程组
- 新增JMeter元组
- 新增监听器
- 运行、查看结果:调试运行,分析指标助局,挖掘性能瓶颈,评估系统性能状态
四、性能优化案例
1、调整堆大小提高服务的吞吐量
设置tomcat堆内存,前后的吞吐量的变化。主要关注Full GC的变化。
2、JVM优化-JIT优化(-XX:+DoEscapeAnalysis)
堆是分配对象的唯一选择吗 ?
可以分配到栈里面,但是在java中没有真正放在堆中,而是把对象分解成聚合量,把聚合量中的标量放入栈中。其实最后都是放入在堆中的。
TaoBaoVM,就使用GCHI技术,把生命周期长的对象,放入本地内存中。
JDK1.6之后,默认开启的。
1、逃逸分析:变量使用仅在方法那,就没有发生逃逸
- 栈上分配:对象没有发生逃逸,就可以分配到栈上
- 同步消除:如果存在只有一个线程调用的 synchronized,就会消除锁
- 标量替换:把对象替换成标量(基本数据类型),存放在栈中(-XX:+EliminateAllocations)
注意,在JVM中,逃逸分析优化的点在于栈上分配的对象使用标量替换。
3、合理配置堆内存
增加内存提高性能是显著的,但是增加了内存就会导致Full GC的时间变得很长,所以增加多少内存合适是需要考量的。
JVM官方给出的意见,如下图所示:
- 堆空间:full GC之后老年代占用内存空间的3~4倍(可以多次Full GC 平均之后)
- 元空间:full GC之后老年代占用内存空间的1.2~1.5倍
- 新生代(-Xmn):full GC之后老年代占用内存空间的1~1.5倍
- 老年代:full GC之后老年代占用内存空间的2~3倍
1、计算老年代存活对象:查看日志、强制出发FullGC
其中查看日志文件比较推荐的方式。看一下多次full GC的平均值。
2、数据分析
根据以上的经验,平时在工作上处理tomcat内存设置都是错误的,不是越大越好,而是根据老年代存活的占用内存大小而设置。
3、估算GC频率
计算出Eden大小:1024*1/3*80%(1G堆空间,1/3年轻代,80%Eden区)
计算出每秒数据大小,两个值相除,就得到大约每隔多少秒进行一次Young GC。
但是计算每秒获取数据量的大小,感觉比较难。
4、新生代和老年代的比例
默认情况下,新生代中Eden:s1:s0=8:1:1,新生代:老年代=1:3。
但是在ParallelGC情况下,查看比例位6:1:1,因为默认开启了动态分配内存中。
结论:
- 对于面向外部的大流量、低延迟系统,不建议开启这个参数 -XX:+UseAdaptiveSizePolicy
- 保持使用 UseParallelGC,显示设置-XX:SurvivorRatio=8
- 使用CMS,直接关闭 -XX:+UseAdaptiveSizePolicy参数即可
5、CPU占用高排查方案(经常出现)
主要是查看哪行代码出现了问题,根据jstack进行排查。
- top 查看哪个进程占用高
- top -Hp 进程ID,查看哪个线程占用高
- jstack 进程ID > 文件,查看进程中线程的堆栈信息
- 线程ID转换成16进制,然后在文件中查看线程堆栈信息
- 使用 jstack 进程ID | grep -A20 线程ID16进制
6、G1并发执行线程数对性能的影响
1、配置信息
当设置ConcGCThreads增加的时候,Jmeter的平均响应时间会有所提高。但是感觉这个值可以直接使用默认即可。
7、调整垃圾回收器提高服务的吞吐量
根据实际情况使用不同的垃圾回收器,例如单核服务器使用SerialGC、高内存的时候使用G1、并发量比较大的时候使用ParallelGC、低延迟的时候使用CMS等等。
8、日均百万级订单交易系统如何设置JVM参数
1、分析
注意一点,因为产生了大量的订单数据,如果一个订单对象需要使用30秒钟,但是每次执行YGC只有10秒钟,可能导致大量的订单对象进入到老年代中,这样的情况需要避免。
也就是量很大,使用时间并不是很长的对象避免出移动到老年代,这样可以防止Full GC的出现,减少STW。
- 在大流量的秒杀系统中,可以使用分布式本地库存+单独服务器做库存均衡
- 升级了服务器变慢了
- 频繁GC导致STW时间长,响应时间慢
- 升级了更卡顿,内存空间越大,做GC时间长,导致STW时间更长
- 更换垃圾回收器:parallelGC、ParNew+GMS、G1,可以考虑实用化G1
- 配置GC参数
- 限制暂停时间
- 配置并发标记线程
- 根据进一步分析,优化内存空间的比例:jstack、jstat、jinfo、jmap
- 内存飙高,如何排查问题
- 导出dump文件,分析dump文件
- 通过相关工具进行内存占比情况,查看GC日志
- 监控JVM
- 命令行工具
- 界面工具