前言
JVM调优的本质:并不是为了显著的提升系统的性能,不是说调优过后,性能就能提升几倍或者十几倍,主要调的是稳定性。如果系统出现了频繁的垃圾回收,这个系统是不稳定的,所以就需要我们来进行jvm调优,调整垃圾回收的频次
一、GC调优原则
1、调优的原则
- 大多数的 java 应用不需要 GC 调优
- 大部分需要 GC 调优的的,不是参数问题,是代码问题
- 在实际使用中,分析 GC 情况优化代码比优化 GC 参数要多得多;
- GC 调优是最后的手段
2、调优的目的
GC 的时间够小
GC 的次数够少
发生 Full GC 的周期足够的长,时间合理,最好是不发生
3、判断是否需要调优的指标
Minor GC 执行时间不到 50ms;
Minor GC 执行不频繁,约 10 秒一次;
Full GC 执行时间不到 1s;
Full GC 执行频率不算频繁,不低于 10 分钟 1 次;
二、GC调优步骤
1、监控gc状态
使用各种 JVM 工具,查看当前日志,分析当前 JVM 参数设置,并且分析当前堆内存快照和 gc 日志,根据实际的各区域内存划分和 GC 执行时间,觉得是否进行优化
2、分析结果,判断是否需要优化
如果各项参数设置合理,系统没有超时日志出现,GC 频率不高,GC 耗时不高,那么没有必要进行 GC 优化;如果 GC 时间超过 1-3 秒,或者频繁 GC,则 必须优化;
3、调整GC类型和内存分配
如果内存分配过大或过小,或者采用的 GC 收集器比较慢,则应该优先调整这些参数,并且先找 1 台或几台机器进行 beta,然后比较优化过的机器和没有 优化的机器的性能对比,并有针对性的做出最后选择;
4、不断的分析和调整
通过不断的试验和试错,分析并找到最合适的参数
5、全面应用参数
如果找到了最合适的参数,则将这些参数应用到所有服务器,并进行后续跟踪。
阅读GC日志
主要关注 MinorGC 和 FullGC 的回收效率(回收前大小和回收比较)、回收的时间。 -XX:+UseSerialGC
- 以参数-Xms5m -Xmx5m -XX:+PrintGCDetails -XX:+UseSerialGC 为例:
[DefNew: 1855K->1855K(1856K), 0.0000148 secs][Tenured: 2815K->4095K(4096K), 0.0134819 secs] 4671K DefNew 指明了收集器类型,而且说明了收集发生在新生代。
1855K->1855K(1856K)表示,回收前 新生代占用 1855K,回收后占用 1855K,新生代大小 1856K。 0.0000148 secs 表明新生代回收耗时。
Tenured 表明收集发生在老年代
2815K->4095K(4096K), 0.0134819 secs:含义同新生代
最后的 4671K 指明堆的大小。 - -XX:+UseParNewGC
收集器参数变为-XX:+UseParNewGC,日志变为:
[ParNew: 1856K->1856K(1856K), 0.0000107 secs][Tenured: 2890K->4095K(4096K), 0.0121148 secs] 收集器参数变为-XX:+ UseParallelGC 或 UseParallelOldGC,日志变为:
[PSYoungGen: 1024K->1022K(1536K)] [ParOldGen: 3783K->3782K(4096K)] 4807K->4804K(5632K),
-XX:+UseConcMarkSweepGC
CMS 收集器和 G1 收集器会有明显的相关字样 -XX:+UseG1GC
三、GC实战
1、项目启动的优化
- 开启日志分析 -XX:+PrintGCDetails 发现有多次 GC 包括 FullGC
- 调整 Metadata 空间 -XX:MetaspaceSize=64m
- 减少 Minor gc 次数,增加参数 -Xms500m
- 减少 Minor gc 次数,调整参数 -Xms1000m
- 增加新生代比重,增加参数 -Xmn900m GC 减少至 1 次
- 加大新生代,调整参数 -Xms2000m -Xmn1800m 还是避免不了 GC,没有必要调整这么大,资源浪费
2、项目运行过程中优化
使用 jmeter 同时访问项目接口进行压测,使用 40 个线程,循环 2500 次进行压力测试,观察并发的变化。
jmeter 的聚合报告的参数解释
2.1使用单线程 GC -XX:+UseSerialGC
吞吐量:11711.9/s
最长耗时请求:401ms
2.2使用多线程GC -XX:+UseParNewGC
吞吐量有一定上升,整体耗时少了一秒
2.3 使用 CMS -XX:+UseConcMarkSweepGC
CMS 采用了并发收集,所以 STW 的时间较小,吞吐量较单线程有一定提高,最大请求时间 MAX 有明显的下降。
2.4使用 G1 -XX:+UseG1GC
G1 这里的吞吐量是最大的,最大请求时间 MAX 有明显的下降。
在请求中new 出一个1M大小的0bject,再进行压测
吞吐量急剧下降,也验证了我们JVM调优的原则
3、推荐策略
3.1新生代大小
- 响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择).在此种情况下,新生代收集发生的频率也是最小 的.同时,减少到达老年代的对象.
- 吞吐量优先的应用:尽可能的设置大,可能到达 Gbit 的程度.因为对响应时间没有要求,垃圾收集可以并行进行,一般适合 8CPU 以上的应用.
- 避免设置过小.当新生代设置过小时会导致:1.MinorGC 次数更加频繁 2.可能导致 MinorGC 对象直接进入老年代,如果此时老年代满了,会触发FullGC.
3.2老年代大小
- 响应时间优先的应用:老年代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数.如果堆设置小了,可 以会造成内存碎 片,高回收频率以及应用暂停而使用传统的标记清除方式; 如果堆大了,则需要较长的收集时间.最优化的方案,一般需要参考以下数据获得:
并发垃圾收集信息、持久代并发收集次数、传统 GC 信息、花在新生代和老年代回收上的时间比例。 - 吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的新生代和一个较小的老年代.原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而 老年代尽存放长期存活对象
jvm优化:逃逸分析
参数:
-XX:+DoEscapeAnalysis:启用逃逸分析(默认打开) -XX:+EliminateAllocations:标量替换(默认打开)
-XX:+UseTLAB 本地线程分配缓冲(默认打开)
如果是逃逸分析出来的对象可以在栈上分配的话,那么该对象的生命周期就跟随线程了,就不需要垃圾回收,如果是频繁的调用此方法则可以得到很大的性能提高(不用触发GC)
采用了逃逸分析–对象在栈上分配:
没有逃逸分析—对象都在堆上分配(触发频次 GC,加重负担):