本文是成为Java GC专家系列文章的第三篇。在第一篇《成为JavaGC专家Part I — 深刻浅出Java垃圾回收机制》中咱们学习了不一样GC算法的执行过程,GC是如何工做的,什么是新生代和老年代,你应该了解的JDK7中的5种GC类型,以及这5种类型对于应用性能的影响。html
在第二篇《成为JavaGC专家Part II — 如何监控Java垃圾回收机制》, 我解释了JVM其实是如何执行垃圾回收的,咱们如何监控GC,以及那哪些具可让咱们的工做更快,更高效。在第三篇文章中,咱们会基于实际的例子来解释 一些优化GC的最佳实践。我认为在阅读本篇文章以前,你已经很好地理解了以前的文章,所以,为了你可以更好地学习本文,若是你尚未读过以前的两篇文章 话,请先阅读。java
为何须要优化GC算法
或者说的更确切一些,对于基于Java的服务,是否有必要优化GC?应该说,对于全部的基于Java的服务,并不老是须要进行GC优化,但前提是所运行的基于Java的系统,包含了以下参数或行为:服务器
已经经过 -Xms 和–Xmx 设置了内存大小并发
包含了 -server 参数oracle
系统中没有超时日志等错误日志jvm
换句话说,若是你没有设定内存的大小,而且系统充斥着大量的超时日志时,你就须要在你的系统中进行GC优化了。ide
可是,你须要时刻铭记一条:GC优化永远是最后一项任务。工具
想一下进行GC优化的最根本缘由,垃圾收集器清除在Java程序中建立的对象,GC执行的次数即须要被垃圾收集器清理的对象个数,与建立对象的数量成正比,所以,首先你应该减小建立对象的数量。性能
俗话说的好,“冰冻三尺非一日之寒”。咱们应该从小事作起,不然日积月累就会很难管理。
咱们须要使用StringBuilder 或者StringBuffer 来替代String
应该尽可能少的输出日志
可是,咱们知道有些状况会让咱们一筹莫展,咱们眼睁睁的看着XML以及JSON解析占用了大量的内存。即使咱们已经尽可 能少的使用String以及尽可能少的输出日志,大量的临时内存被用于XML或者JSON解析,例如10-100MB。可是,舍弃XML和JSON是很难 的。咱们只要知道,他会占用不少内存。
若是应用内存使用量通过几回重复调整以后有所改善,你就能够开始GC优化了。
我为GC优化概括了两个目的:
一个是将转移到老年代的对象数量降到最少
另外一个是减小Full GC的执行时间
将转移到老年代的对象数量降到最少
按代的GC机制由Oracle JVM提供,不包括能够在JDK7以及更高版本中使用的G1 GC。换句话说,对象被建立在伊甸园空间,然后转化到幸存者空间,最终剩余的对象被送到老年代。某些比较大的对象会在被建立在伊甸园空间后,直接转移到老 年代空间。老年代空间上的GC处理会新生代花费更多的时间。所以,减小被移到老年代对象的数据能够显著地减小Full GC的频率。减小被移到老年代空间的对象的数量,可能被误解为将对象留在新生代。可是,这是不可能的。取而代之,你能够调整新生代空间的大小。
减小Full GC执行时间
Full GC的执行时间比Minor GC要长不少。所以,若是Full GC花费了太多的时间(超过1秒),一些链接的部分可能会发生超时错误。
若是你试图经过消减老年代空间来减小Full GC的执行时间,可能会致使OutOfMemoryError 或者 Full GC执行的次数会增长。
与之相反,若是你试图经过增长老年代空间来减小Full GC执行次数,执行时间会增长。
所以,你须要将老年代空间设定为一个“合适”的值。
影响GC性能的参数
正如咱们在第二篇文章结尾提到的,不要幻想“某我的设定了GC参数后性能获得极大的提升,咱们为何不和他用同样的参数?”,由于不一样的Web服务所建立对象的大小和他们的生命周期都不尽相同。
简单来讲,若是一个任务的执行条件是A,B,C,D和E,一样的任务执行条件换为A和B,你会以为哪一个更快?从通常人的直觉来看,在A和B条件下执行的任务会更快。
Java GC参数也是相同的道理,设定一些参数不但没有提升GC执行速度,反而可能致使他更慢。GC优化的最基本原则是将不一样的GC参数用于2台或者多台服务器,并进行对比,并将那些被证实提升了性能或者减小了GC执行时间的参数应用于服务器。请谨记这一点。
下面这个表格列出了GC参数中与内存大小相关的,能够影响性能的参数。
表1:GC优化须要考虑的Java参数
定义 |
参数 |
描述 |
堆内存空间 |
-Xms |
Heap area size when starting JVM 启动JVM时的堆内存空间。 |
-Xmx |
Maximum heap area size 堆内存最大限制 |
|
新生代空间 |
-XX:NewRatio |
Ratio of New area and Old area 新生代和老年代的占比 |
-XX:NewSize |
New area size 新生代空间 |
|
-XX:SurvivorRatio |
Ratio ofEdenarea and Survivor area 伊甸园空间和幸存者空间的占比 |
我在进行GC优化时常用-Xms,-Xmx和-XX:NewRatio。-Xms和-Xmx是必须的。你如何设定NewRatio 会对GC性能产生十分显著的影响。有些人可能会问如何设定Perm区域的大小?你能够经过-XX:PermSize 和-XX:MaxPermSize参数来设定,
当OutOfMemoryError 错误发生而且是因为Perm空间不足致使时,另外一个可能影响GC性能的参数是GC类型。下表列出了全部可选的GC类型(基于JDK6.0)
表2:GC类型可选参数
分类 |
参数 |
备考 |
Serial GC |
-XX:+UseSerialGC |
|
Parallel GC |
-XX:+UseParallelGC |
|
Parallel Compacting GC |
-XX:+UseParallelOldGC |
|
CMS GC |
-XX:+UseConcMarkSweepGC |
|
G1 |
-XX:+UnlockExperimentalVMOptions |
在JDK6中这两个参数必须同时使用 |
除了G1 GC,能够经过每种类型第一行的参数来切换GC类型。最经常使用的GC类型是Serial GC。他专门针对客户端系统进行了优化。
影响GC性能的参数有不少,可是上面提到的参数会带来最显著的效果。请牢记,设定过多的参数不必定会减小GC执行时间。
GC优化过程
GC优化的过程与大多数性能改善的过程及其相似。下面是我使用的GC优化过程。
1.监控GC状态
首先你须要监控GC来检查在系统执行过程当中GC的各类状态。请参考前一篇文章中提到的监控方法 成为JavaGC专家Part II — 如何监控Java垃圾回收机制。
2.在分析监控结果后,决定是否进行GC优化
在检查GC状态的过程当中,你应该分析监控结果以便决定是否进行GC优化,若是分析结果代表执行GC的时间只有0.1-0.3秒,那你就不必浪费时间去进行GC优化。可是,若是GC的执行时间是1-3秒,或者超过10秒,GC将势在必行。
可是,若是你已经为Java分配了10GB的内存,而且不能再减小内存大小,你将没法再对GC进行优化。在进行GC优化 以前,你必须想清楚你为何要分配如此大的内存空间。假如当你分1 GB 或 2 GB内存时出现OutOfMemoryError ,你应该执行堆内存转储(heap dump),并消除隐患。
注意:
堆内存转储是一个用来检查Java内存中的对象和数据的文件。该文件能够经过执行JDK中的jmap命令来建立。在建立文件的过程当中,Java程序会暂停,所以不要再系统执行过程当中建立该文件。
你能够在互联网上搜索堆内存[s1] 转储的详细说明。对于韩国的读者,能够参考我去年发布的书: The story of troubleshooting for Java developers and system operators (Sangmin Lee, Hanbit Media, 2011, 416 pages)。
3. 调整GC类型/内存空间
若是你已经决定要进行GC优化,那么就要选择GC类型和设定内存空间。在这时,若是你有几台不一样服务器,请时刻牢记,检查每一台服务器的GC参数,并进行有针对性的优化。
4.分析结果
在调整了GC参数并持续收集24小时以后,开始对结果进行分析,若是你幸运的话,你就找到那些最适合系统的GC参数。反之,你须要经过分析日志来检查内存是如何被分配的。而后你须要经过不断的调整GC类型和内存空间大小一边找到最佳的参数。
5. 若是结果使人满意,你能够将该参数应用于全部的服务器,并中止GC优化
有过GC优化结果使人满意,你能够应用于全部的服务器,下面的章节中,咱们将看到每一个步骤的具体任务。
监控GC状态及分析结果
查看运行中的Web Application Server (WAS)的GC状态的最佳方法是经过jstat命令,在第二篇文章成为JavaGC专家Part II — 如何监控Java垃圾回收机制中我已经详细解释过jstat命令,所以本篇文章我将重点描述数据部分。
下面这个例子展示了某个JVM在进行GC优化以前的状态。
(很遗憾,这不是一个操做服务器)
1
2
3
4
|
$ jstat -gcutil 21719 1s
S0 S1 E O P YGC YGCT FGC FGCT GCT
48.66 0.00 48.10 49.70 77.45 3428 172.623 3 59.050 231.673
48.66 0.00 48.10 49.70 77.45 3428 172.623 3 59.050 231.673
|
如上表,咱们先看一下YGC 和YGCT,计算YGCT/ YGC获得0.050秒(50毫秒)。这意味着新生代空间上的GC操做平均花费50毫秒。在这种状况,你大可没必要担忧新生代空间上执行的GC操做。
接下来,咱们来看一下FGCT 和FGC。,计算FGCT/ FGC获得19.68秒,这意味着GC的平均执行时间为19.68秒,多是每次花费19.68秒执行了三次,也多是其中的两次执行了1秒而另外一次执行了58秒。不论哪一种状况,都须要进行GC优化。
经过jstat 命令能够很轻易地查看GC状态,可是,分析GC的最佳方式是经过–verbosegc参数来生成日志,在以前的文章中我已经解释了如何分析这些日志,HPJMeter 是我我的最喜欢的用于分析-verbosegc 日志的工具。他很易于使用和分析结果。经过HPJmeter你能够很轻易查看GC执行时间以及GC发生频率。若是GC执行时间知足下面全部的条件,就意味着无需进行GC优化了。
Minor GC执行的很快(小于50ms)
Minor GC执行的并不频繁(大概10秒一次)
Full GC执行的很快(小于1s)
Full GC执行的并不频繁(10分钟一次)
上面提到的数字并非绝对的;他们根据服务状态的不一样而有所区别,某些服务可能知足于Full GC每次0.9秒的速度,但另外一些可能不是。所以,针对不一样的服务设定不一样的值以决定是否进行GC优化。
在查看GC状态的时候有件事你须要特别注意,那就是不要只关注Minor GC 和Full GC的执行时间。还要关注GC执行的次数,例如,当新生代空间较小时,Minor GC会过于频繁的执行(有时每秒超过1次)。另外,转移到老年代的对象数增多,则会致使Full GC执行次数增多。所以,别忘了加上–gccapacity参数来查看具体占用了多少空间。
设定GC类型/内存空间大小
设定GC类型
OracleJVM有5种GC类型,可是在JDK7以前的版本中,只能在Parallel GC, Parallel Compacting GC 和CMS GC之中选择一个,对于选择哪一个没有明确的原则和规则。
这样的话,咱们该如何选择呢?强 烈建议三者都选,可是,有一点是很明确的:CMS GC比Parallel GCs更快。若是真的如此,那么就选CMS GC了。可是,CMS GC也不老是更快。总体来看,CMS GC模式下的Full GC执行更快,不过,一旦出现并行模式失败,他将比Parallel GC更慢。
并发模式失败
咱们来详细讲解一下并发模式失败。
Parallel GC 和 CMS GC 最大的不一样来自于压缩任务。压缩任务是经过删除已分配内存空间中的空白空间以便压缩内存,清理内存碎片。
在Parallel GC模式下,压缩工做在Full GC执行时进行,这会费不少时间,可是,在执行完Full GC以后,因为可以顺序地分配空间,随后的内存可以被更快的分配。
与之相反的,CMS GC并不进行压缩处理,所以,CMS GC执行的更快。可是,因为没有压缩,在进行磁盘清理以前,内存中会有不少空白空间。这就是说,可能没有足够的空间存储大的对象,例如,虽然老年代空间还 有300MB空间,可是一些10MB的对象没法被顺序的存储。在这种状况下,会出现“并行模式失败”警告,并执行压缩处理。在CMS GC模式下,压缩处理的执行时间要比Parallel GCs长不少。另外,这还将致使另一个问题。关于并发模式失败的详细说明,能够参考Oracle工程师撰写的Understanding CMS GC Logs。
综上所述,你须要找到最适合你的系统的GC类型。
每一个系统都有最适合他的GC类型等着你去寻找,若是你有6台服务器。我建议你每两台设置相同的参数。并添加 –verbosegc参数,分析结果。
设定内存空间大小
下表展现了内存空间大小,GC执行次数以及GC执行时间三者间的关系。
大内存空间
减少GC执行次数
增长GC执行时间
小内存空间
减少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都应该是一致的。不过,即使对于专业测试人员要想精确的控制负载也是很难的,并要花费大量的时间准备。因 此,相对来讲比较方便和容易的方法是调整才参数,以后花费较长的时间收集结果。
分析GC优化结果
在设置了GC参数以及-verbosegc参数以后,经过tail命令确保日志被正确的生成。若是参数设置的不正确或者日志没有生成,你将白白浪费你的时间。若是日志正确的话,持续收集1到2天。随后最好将日志下载到本地PC并用HPJMeter来分析
Full GC 执行时间
Minor GC执行时间
Full GC 执行间隔
Minor GC 执行间隔
Entire Full GC 执行时间
Entire Minor GC 执行时间
Entire GC 执行时间
Full GC e执行时间
Minor GC 执行时间
找到最佳的GC参数是件很是幸运的事情,然而在大多数场合,咱们并不会获得幸运之神的眷顾,在进行GC优化时要尽可能当心谨慎,想一步完成优化每每会致使OutOfMemoryError 。
优化示例
好了,咱们一直在纸上谈兵,如今咱们看一些实际的GC优化的例子。
示例1
下面这个例子针对 Service S的优化,对于最近被部署的 Service S,Full GC花费了太长的时间。
请看 jstat –gcutil的执行结果。
1
2
|
S0 S1 E O P YGC YGCT FGC FGCT GCT
12.16
0.00
5.18
63.78
20.32
54
2.047
5
6.946
8.993
|
最左边的Perm 空间对于最初的GC优化不是很重要,这一次YGC参数的值更加有用。
Minor GC和Full GC的平均值以下表所示
表3:Service S的Minor GC 和Full GC的平均执行时间
GC 类型 |
GC 执行次数 |
GC 执行时间 |
平均 |
Minor GC |
54 |
2.047 |
37 ms |
Full GC |
5 |
6.946 |
1,389 s |
最重要的是下面两个数据
新生代实际使用空间: 212,992 KB
老年代实际使用空间: 1,884,160 KB
所以,总的内存空间为2GB,不算Perm空间的话,新生代与老年代之比为1:9。经过jstat和-verbosegc 日志进行数据收集,并把三台服务器按照以下方式设置。
NewRatio=2
NewRatio=3
NewRatio=4
一天以后,检查系统的GC日志后发现,在设置了NewRatio参数后很幸运的没有发生Full GC,
为何?
NewRatio=2: 45 ms
NewRatio=3: 34 ms
NewRatio=4: 30 ms
咱们看到NewRatio=4 是最佳的参数,虽然它的新生代空间最小,但GC时间确最短。设定这个参数以后,系统没有执行过Full GC。
为了说明这个问题,下面是服务之星一段时间后执行jstat –gcutil的结果
1
2
|
S0 S1 E O P YGC YGCT FGC FGCT GCT
8.61
0.00
30.67
24.62
22.38
2424
30.219
0
0.000
30.219
|
你可能会认为由于服务器接受的请求少才致使的GC执行频率降低。实际上,虽然Full GC没有执行,可是Minor GC被执行了 2,424次。
示例2
这是一个针对ServiceA的例子,咱们经过公司内部的应用性能管理系统(APM)发现JVM暂停了至关长的时间(超过8秒),所以咱们进行了GC优化。咱们找到了Full GC执行时间过长的缘由,并着手解决。
进行GC优化的第一步,就是咱们添加了-verbosegc参数,并获得以下结果。
图1:进行GC优化以前的STW时间
如上图所示,由HPJMeter自动生成的图片之一。X坐标表示JVM执行的时间。Y坐标表示每次GC的时间。CMS绿点,表示Full GC结果。Parallel Scavenge蓝点,表示Minor GC结果。
以前我曾经说过CMS GC是最快的,可是上面的的结果显示出于某种缘由,它最多花费了15秒。是什么致使这个结果?是否想起我以前提过的,CMS在进行内存清理时,会变慢。与此同时,服务的内存被设定为 –Xms1g和–Xmx4g ,且实际分配了4GB内存。
所以,我将GC类型从CMS改成Parallel GC。而且将内存改成2GB,设定NewRatio 为3。几小时以后我使用 jstat –gcutil获得以下结果
1
2
|
S0 S1 E O P YGC YGCT FGC FGCT GCT
0.00
30.48
3.31
26.54
37.01
226
11.131
4
11.758
22.890
|
相对于4GB时的15秒,Full GC变成了平均每次3秒。可是3秒同样比较慢,所以我设计了以下6种场景。
Case 1: -XX:+UseParallelGC -Xms1536m -Xmx1536m -XX:NewRatio=2
Case 2: -XX:+UseParallelGC -Xms1536m -Xmx1536m -XX:NewRatio=3
Case 3: -XX:+UseParallelGC -Xms1g -Xmx1g -XX:NewRatio=3
Case 4: -XX:+UseParallelOldGC -Xms1536m -Xmx1536m -XX:NewRatio=2
Case 5: -XX:+UseParallelOldGC -Xms1536m -Xmx1536m -XX:NewRatio=3
Case 6: -XX:+UseParallelOldGC -Xms1g -Xmx1g -XX:NewRatio=3
那一个最快呢?结果显示,内存越小,结果越好。下图展现了Case6的结果。这是GC的性能最好。最长的响应时间只有1.7秒。平均时间在1秒以内。
图2:Case6的时间图表
基于以上结果。咱们按照Case6调整了GC参数。可是,这致使了天天晚上都会发生OutOfMemoryError。在这里很难解释具体的缘由。简单来讲,批处理程序致使了内存泄漏。相关的问题已经被解决。
若是对GC日志只分析很短的时间就贸然对全部服务器进行优化是很是危险的。请时刻牢记,你必须同时分析GC日志和应用程序。
咱们回顾了两个关于GC优化的例子,正如我以前提到的,例子中提到的GC参数,能够设置在相同的服务器之上,但前提是他们具备相同的CPU,操做系统,JDK版本以及运行着相同的服务。可是不要直接把我用过的参数用到你的服务至上,它们未必能很好的工做。
结论
我凭借经验进行GC优化,而没有执行堆转储并分析内存的详细内容。精确地分析内存能够获得更好的优化效果。可是,这种分析通常适用于内存使用量相对固定的场合。不过,若是服务严重过载并占用的大量的内存,强力建议根据以前的经验进行GC优化。
我已经在一些服务上设置了G1 GC参数,并进行过性能测试。但尚未应用与正式环境,G1 GC参数的速度要快于其余任何GC类型。可是,你必需要升级到JDK7。另外,他的稳定性也暂时没有保障,没人知道是否会出现致命的错误。所以还不到将其正式应用的时候
在将来的某一天,等到JDK7真正稳定了(这不是说他如今不稳定),而且WAS针对JDK7进行优化后,G1 GC最终可以按照预期的那样工做了,咱们可能就不须要在进行GC优化了。
想了解GC优化的更多内容,请登陆Slideshare.com 查看关联资源。强烈推荐Everything I Ever Learned About JVM Performance Tuning @Twitter 。做者Attila Szegedi,一位Twitter工程师。请花些时间阅读。
By Sangmin Lee, NHN Performance Engineering Lab.
做者Sangmin Lee,就任于NHN性能工程研究院
译文地址: http://www.importnew.com/3146.html
本文转自:http://www.importnew.com/3146.html