JAVA的GC是面试必考的题目,但是在实际项目中何时使用GC哪?或者应该何时优化GC哪?有句名言:“GC优化永远是最后一项任务”。web
在使用GC以前,应该考虑一下进行GC的最根本缘由:垃圾收集器须要清除在程序中建立的对象,GC执行的次数即须要被垃圾收集器清理的对象个数,与建立对象的数量成正比,所以,首先应该减小建立对象的数量,咱们应该从小事作起,好比须要使用StringBuilder 或者StringBuffer 来替代String;应该尽可能少的输出日志;面试
可是,咱们知道有些状况会让咱们一筹莫展,咱们眼睁睁的看着XML以及JSON解析占用了大量的内存。即使咱们已经尽量少的使用String以及尽可能少的输出日志,可是大量的临时内存仍然被用于XML或者JSON解析,例如10-100MB。可是,舍弃XML和JSON是很难的;或者程序偶尔内存溢出;或者假死影响到了正常的服务,这个时候就能够考虑优化GC了。服务器
那么优化GC能够首相到两个方面:一、一个是将转移到老年代的对象数量降到最少;二、另外一个是减小Full GC的执行时间。性能
分代垃圾回收策略是由Oracle JVM提供,不包括能够在JDK7以及更高版本中使用的G1 GC。换句话说,对象被建立在伊甸园空间,然后转化到幸存者空间,最终剩余的对象被送到老年代。某些比较大的对象会在被建立在伊甸园空间后,直接转移到年老代空间。年老代空间上的GC处理会年轻代花费更多的时间。所以,减小被移到年老代对象的数据能够显著地减小Full GC的频率。减小被移到年老代空间的对象的数量,可能会被误解为将对象留在新生代。可是,这是不可能的。取而代之,能够调整新生代空间的大小。测试
Full GC的执行时间比Minor GC要长不少。所以,若是Full GC花费了太多的时间(超过1秒),一些链接的部分可能会发生超时错误。这个时候若是你单纯试图经过减小年老代空间来减小Full GC的执行时间,可能会致使OutOfMemoryError 或者 Full GC执行的次数会增长。与之相反,若是你试图经过增长老年代空间来减小Full GC执行次数,执行时间又会增长。所以,你须要将老年代空间设定为一个“合适”的值。优化
不要幻想“某我的设定了GC参数后性能获得极大的提升,咱们为何不和他用同样的参数?”,由于不一样的Web服务所建立对象的大小和他们的生命周期都不尽相同。ui
Java GC参数设定一些参数不但没有提升GC执行速度,反而可能致使他更慢。GC优化的最基本原则是将不一样的GC参数用于2台或者多台服务器,并进行对比,并将那些被证实提升了性能或者减小了GC执行时间的参数应用于服务器。下面这个表格列出了GC参数中与内存大小相关的,能够影响性能的参数:spa
表1:GC优化须要考虑的Java参数日志
定义 | 参数 | 说明 |
堆内存 | -Xms | 启动JVM时的堆内存空间大小 |
-Xmx | 堆内存的最大值 | |
年轻代 | -XX:NewRatio | 年轻代与年老代的比值 |
-XX:NewSize | 年轻代大小 | |
-XX:SurvivorRatio | 伊甸园空间和幸存者空间的比值 |
我在进行GC优化时常用-Xms,-Xmx和-XX:NewRatio。另外一个可能影响GC性能的参数是GC类型。下表列出了全部可选的GC类型(基于JDK6.0)。orm
分类 | 参数 | 备注 |
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 | 在JDK6中这两个参数必须同时使用 |
-XX:+UseG1GC |
GC优化的过程与大多数性能改善的过程及其相似。下面是使用的GC优化过程:
1.监控GC状态
首先你须要监控GC来检查在系统执行过程当中GC的各类状态。
2.在分析监控结果后,决定是否进行GC优化
在检查GC状态的过程当中,你应该分析监控结果以便决定是否进行GC优化,若是分析结果代表执行GC的时间只有0.1-0.3秒,那你就不必浪费时间去进行GC优化。可是,若是GC的执行时间是1-3秒,或者超过10秒,GC将势在必行。
3. 调整GC类型/内存空间
若是你已经决定要进行GC优化,那么就要选择GC类型和设定内存空间。在这时,若是你有几台不一样服务器,请时刻牢记,检查每一台服务器的GC参数,并进行有针对性的优化。
4.分析结果
在调整了GC参数并持续收集24小时以后,开始对结果进行分析,若是你幸运的话,你就找到那些最适合系统的GC参数。反之,你须要经过分析日志来检查内存是如何被分配的。而后你须要经过不断的调整GC类型和内存空间大小一边找到最佳的参数。
5. 若是结果使人满意,你能够将该参数应用于全部的服务器,并中止GC优化
有过GC优化结果使人满意,你能够应用于全部的服务器,下面,咱们将看到每一个步骤的具体任务。
查看运行中的Web Application Server (WAS)的GC状态的最佳方法是经过jstat命令,下面这个例子展示了某个JVM在进行GC优化以前的状态。
如上表,咱们先看一下YGC 和YGCT,计算YGCT/YGC,172.623/3428=0.050秒(50毫秒)。这意味着新生代空间上的GC操做平均花费50毫秒。在这种状况,你大可没必要担忧新生代空间上执行的GC操做。接下来,咱们来看一下FGCT 和FGC。计算FGCT/ FGC获得19.68秒,这意味着GC的平均执行时间为19.68秒,多是每次花费19.68秒执行了三次,也多是其中的两次执行了1秒而另外一次执行了58秒。不论哪一种状况,都须要进行GC优化。
经过jstat 命令能够很轻易地查看GC状态,可是,分析GC的最佳方式是经过–verbosegc参数来生成日志。若是GC执行时间知足下面全部的条件,就意味着无需进行GC优化了:
上面提到的数字并非绝对的;他们根据服务状态的不一样而有所区别,某些服务可能知足于Full GC每次0.9秒的速度,但另外一些可能不是。所以,针对不一样的服务设定不一样的值以决定是否进行GC优化。
在查看GC状态的时候有件事你须要特别注意,那就是不要只关注Minor GC 和Full GC的执行时间。还要关注GC执行的次数,例如,当新生代空间较小时,Minor GC会过于频繁的执行(有时每秒超过1次)。另外,转移到老年代的对象数增多,则会致使Full GC执行次数增多。所以,别忘了加上–gccapacity参数来查看具体占用了多少空间。
1.设定GC类型
OracleJVM有5种GC类型,可是在JDK7以前的版本中,只能在Parallel GC, Parallel Compacting GC 和CMS GC之中选择一个,对于选择哪一个没有明确的原则和规则,可是有一点是很明确的:CMS GC比Parallel GCs更快。
可是,CMS GC也不老是更快。总体来看,CMS GC模式下的Full GC执行更快,不过,一旦出现并行模式失败,他将比Parallel GC更慢。
CONCURRENT MODE失败
在说明这个问题以前,首先要明白Parallel GC 和 CMS GC 最大的不一样来自于压缩任务。压缩任务是经过删除已分配内存空间中的空白空间以便压缩内存,清理内存碎片。在Parallel GC模式下,压缩工做在Full GC执行时进行,这会费不少时间,可是,在执行完Full GC以后,因为可以顺序地分配空间,随后的内存可以被更快的分配。
与之相反的,CMS GC并不进行压缩处理,所以,CMS GC执行的更快。可是,因为没有压缩,在进行磁盘清理以前,内存中会有不少空白空间。这就是说,可能没有足够的空间存储大的对象,例如,虽然老年代空间还有300MB空间,可是一些10MB的对象没法被顺序的存储。在这种状况下,会出现“并行模式失败”警告,并执行压缩处理。在CMS GC模式下,压缩处理的执行时间要比Parallel GCs长不少。这样CMS还不准Parallel GCs效率高。综上所述,你须要找到最适合你的系统的GC类型。每一个系统都有最适合他的GC类型等着你去寻找,若是你有6台服务器。我建议你每两台设置相同的参数。并添加 –verbosegc参数,分析结果。
2.设定内存空间大小
下面特别精辟的展现了内存空间大小、GC执行次数和GC执行时间三者间的关系:
关于如何设置内存空间的大小,没有惟一的标准答案。若是服务器资源足够,并且Full GC也可能在1秒内完成,设置为10GB固然可行。但绝大多数服务器并非这样,当内存设为10GB时,可能要花费10~30秒来执行Full GC。固然,执行时间会随对象的大小而改变。
鉴于如此,咱们应该如何设定内存空间大小呢?通常来讲,我建议为500MB。不过请注意这不是让你将WAS的内存参数设置为–Xms500m 和–Xmx500m。根据优化GC以前的状态,若是Full GC执行以后内存空间剩余300MB,那么最好将内存设置为1GB(300MB(默认程序占用)+ 500MB(老年代最小空间)+200MB(空闲内存))。也就是说你要为老年代额外设置500MB。所以,若是你有三个执行服务器,内存分别设置为1GB,1.5GB,2GB,而且检查结果。
理论上来说,GC执行速度应该遵循1GB> 1.5GB> 2GB,所以1GB执行GC速度最快。可是并不说明1GB空间的Full GC会花费1秒而2GB空间会花费2秒。时间取决于服务器的性能和对象的大小。所以,最佳的方式是创建尽量多的衡量指标来监控他们。
对于内存空间大小,你应该额外设定NewRatio参数。NewRatio参数是新生代和老年代空间的比例,即XX:NewRatio=1意味着新生代与老年代之比为1:1。对于1GB来讲就是新生代和老年代各500MB。若是NewRatio为2,意味着新生代老年代之比为1:2,所以该值越大,老年代空间越大,新生代空间越小。
这看似一件不是很重要的事情,但NewRatio参数会显著地影响整个GC的性能。若是新生代空间很小,会用更多的对象被转移到老年代空间,这样致使频繁的Full GC,增长暂停时间。你能够简单的认为NewRatio 为1是最佳的选择,可是,有时可能设置为2或3更好,我就见过不少这样的例子。
如何最快的完成GC优化?对比性能测试的结果应该是最快地方法,为每一台服务器设置不一样的参数并监控他们的状态,强烈建议至少监控1或2天的数据。可是,当你对GC优化是,你要确保每次执行相同的负载。而且请求的比率,例如URL都应该是一致的。不过,即使对于专业测试人员要想精确的控制负载也是很难的,并要花费大量的时间准备。所以,相对来讲比较方便和容易的方法是调整才参数,以后花费较长的时间收集结果。