调优实战
# 7.1 环境
- 我们依然使用上面收集器章节相同的案例,这次针对内存来做详细分析。
- 使用企业里主流的jdk8运行,其他jdk版本参数可能略有不同。
- jdk自带的分析命令和工具给大家准备了扩展资料
- 这里我们使用在线、简洁、高效的图形化分析工具: http://gceasy.io
- gc日志参数:
-XX:+PrintGC 输出GC日志
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
-Xloggc:gc.log jdk8日志文件的输出
-Xlog:gc*:gc.log jdk11日志输出方式略有不同
# 7.2 初始状态
# 7.2.1 参数
-Xmx32m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:gc.log
# 7.2.2 执行日志
运行一段时间后,在项目的目录下会看到gc.log
# 7.2.3 日志分析
打开 gceasy 的主页,上传后点击 Analyze 即可看到分析结果
1)整体内存概况
young与old全部吃满,均明显不足,需要调大,meta闲置,可以缩减
2)整体时间情况
收集时间绝大多数在10ms以内,说明收集的速度还可以,但是回收次数明显偏多,整体吞吐量94
3)回收前后的heap明显抖动频繁,整体偏高
4)GC间隔时间长的点,发生大量FullGC
5)大批量空间的有效释放在FullGC上
6)回收释放情况
年轻代和年老代回收效果一般,回收前后的两条线甚至发生交叉,应该偏离较远才说明有明显内存下降
Metaspace比较稳定
7)A&P:对象从年轻代晋升到老年代的情况
频繁晋升,跨代移动,说明年轻代不够用,老年代一旦堆积,极容易引发fullGC
8)GC次数及时间统计
发生591次GC,里面竟然有45是FullGC,不可容忍!
总GC耗时,2.820s,FullGC占了将近一半
9)GC停顿情况
总耗时、平均每次GC耗时 4.77ms
10)GC被触发的原因
常规达到阈值垃圾回收45次。
内存分配失败引发回收,这个会影响正常业务执行。
# 7.3 初步调优
# 7.3.1 参数
分析上面的情况,明显年轻代老年代内存均严重不足,那么最简单粗暴的方式,我们加大内存
-Xms256m -Xmx256m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:gc.log
# 7.3.2 二次分析
重复上面的步骤,新开一个浏览器页签,方便对比分析日志,重点关注几个点:
1)总内存够用了,但是年轻代依然被吃爆。年老代闲置。
2)吞吐量上升,耗时特别长的gc分部区间明显减少,甚至消失
3)gc前后的空间曲线对比明显
4)FullGC消失!
5)GC大批量的内存释放发生在了年轻代
6)年轻代的回收前后两条曲线不再交叉,被明显剥离
7)年老代表示情绪稳定
8)年轻到老年代的晋升明显减少
9)FullGC完全消失,总GC次数明显减少到49,总停顿时间从上次的2.8s降低到0.3s
10)晋升的对象明显减少,创建速度提升
11)不再发生内存分配失败造成gc的现象
# 7.4 二次调优
# 7.4.1 参数
结合上次调优,我们发现,年轻代依然不够用,年老代闲置,对象还是会频繁从年轻代晋升到年老代。
结合我们的业务场景,大批量对象在请求后会被释放,属于短生命周期。包括我们现实中从数据库请求发送到网页后对象就完成了实名,属于同类场景。
所以,加大年轻代比例!
-Xms256m -Xmx256m -XX:NewSize=250m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:gc.log
# 7.4.2 日志分析
同样是256的内存,我们再次跑出日志分析看看差异
1)年轻代已基本够用,很少有对象再跑到老年代
2)吞吐量进一步上升
思考一下,那为什么pause的平均时间还变长了呢???
答:次数变少了,单次需要收集的对象多了,所以肯定要占时间,我们接着往下看总耗时!
3)堆回收空间变化明显
4)gc次数明显下降到8次,总时间进一步降低到80ms
# 7.5 小结
- 机器主要用来跑java服务的话,一般是需要调优的。因为默认堆最大只占物理内存的1/4
- jvm的调优没有标准可言,不同业务场景的内存占用和增长情况不同。调整需要根据结果一步步来,直到最优。
- 调优工具很多,gceasy属于简单直观的ui操作,jvm自带工具大多是命令行且功能较少,在扩展资料里。
- parallel是jdk8下的默认收集器,切换不同收集器后的调试与上面过程一致,感兴趣的同学可以逐个尝试。