有必要优化GC吗?
确切的说是 基于Java的应用一定需要进行GC优化吗?我认为并非所有基于Java的应用都需要进行GC优化,例如基于Java的系统有如下参数或行为:
- 已经通过-Xms 和 -Xmx 指定了内存大小
- 包含了 -server 参数
- 系统中未出现 超时 等日志
换句话说,如果你没有设置内存大小而且出现了大量超时日志,那么你需要在系统中进行GC优化了。
但是有件事要铭记于心:GC优化是你最后的手段。
思考下GC优化的根本原因:Java中创建的对象由垃圾收集器来清理,同时待清理对象的数量和各类GC的执行次数又和创建对象总数量成正比。因此,为了控制GC的执行,首先要做的是 减少创建对象的总数量。
俗话说,”积少成多”。我们需要关注一些小事情,否则”养成气候”之后将难以驾驭。例如:
- 使用StringBuilder或StringBuffer代替String
- 尽可能少的输出日志
如果在几次参数调整后内存使用情况有所改善,你就可以进行GC优化了。我将GC优化的目的分成两类:
- 将转移到老年代的对象数量降到最少
- 减少Full GC的执行时间(减少stop the word发生次数)
将转移到老年代的对象数量降到最少
Oracle JVM提供了分代垃圾回收机制(JDK1.7及以上的G1 GC除外)。换句话说,对象创建在Eden区,然后在Survivor的From和To区之间移动,最后存活的对象被转移到老年代。一些大对象在Eden区创建之后被直接转移到老年代。相对新生代,老年代的GC消耗的时间更长。因此,减少从新生代转移到老年代的对象数量可以降低Full GC的频率。
减少从新生代转移到老年代对象的数量的说法容易造成误解,而且也不可能,但可以通过 调整年轻代的大小 来实现。
减少Full GC的时间
和Minor GC相比,Full GC的执行时间长很多。因此,如果执行Full GC的时间过长(超过1s),将导致连接服务的请求超时。
如果通过减少老年代的大小来降低Full GC执行时间,会造成OutOfMemoryError或增加Full GC的次数
如果老年代设置过小,就会频繁触发full gc,full gc是非常耗时的。年轻代在经过n(hotspot默认是15)轮后会进入老年代,这样老年代顶不住了,就会触发full gc,回收时需要stop the world,这样系统经常发生长时间停顿,影响系统的吞吐量。
如果增大老年代大小以期减少Full GC的执行次数,那么执行时间又会增加
因此,需要合理的设置 老年代大小。
stop-the-world事件耗时很长
不合适的GC选项可能导致stop-the-world时间过长,你可以通过分析器或heap dump来定位问题。
这意味着你可以在检查堆中对象类型和数量后判断原因。如果你发现很多不必要的对象,你最好修改源码,如果创建对象过程中没有什么特别的问题,最好直接变更GC参数。
影响GC性能的参数
不要去想 “有人在使用一些GC参数后性能显著提升,为什么我们不使用相同的参数?“,原因是 不同Web应用中对象的大小和生命周期不同。
对于Java GC参数的设置,设置多个参数并不会提高GC的执行速度,恰恰相反,可能会降低执行速度。GC优化的基本原则是:将不同的GC参数应用到2个或多个主机,然后比对结果,最后将性能最优的参数组合推广到其他主机,这点必须铭记于心。
下表是一些影响GC性能的参数。
表1: GC优化时需要检查的JVM参数
分类 参数 描述
堆区 -Xms 启动JVM时的初始堆大小
-Xmx 最大堆内存
新生代 -XX:NewRatio 新生代和老年代内存大小比例
-XX:NewSize 新生代大小
-XX:SurvivorRatio Eden和Survivor区的比率
我经常使用 -Xms、-Xmx、-XX:NewRatio 三个参数来进行GC调优。-Xms、-Xmx 是肯定需要的,-XX:NewRatio 的设置将会显著的影响GC性能。
有的人可能会问 如何设置Perm区大小? 你可以通过 -XX:PermSize、-XX:MaxPermSize 设置,这个会与Perm区 OutOfMemoryError相关。
另一个会影响GC性能的是 GC类型,下面是基于JDK1.6可选的GC类型:
类别 参数 备注
Serial GC -XX:+UseSerialGC
Parallel GC -XX:+UseParallelGC
-XX:ParallelGCThreads=value
Parallel Compacting GC -XX:+UseParallelOldGC
CMS GC -XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
-XX:+CMSParallelRemarkEnabled
-XX:CMSInitiatingOccupancyFraction=value
-XX:+UseCMSInitiatingOccupancyOnly
G1 -XX:+UnlockExperimentalVMOptions
-XX:+UseG1GC 在JDK6,这两个参数必须同时使用
除G1 GC外,GC类型可以通过第一行的参数来切换。最常见的GC类型是 Servial GC,它针对客户端系统专门进行了优化。
影响GC性能的参数有很多,但是上面的参数有着最为显著的影响。记住,设置过多的参数并不能保证一定会缩短GC的时间。
参考:https://chenyongjun.vip/articles/56