成为Java GC专家系列(3) — 如何优化Java垃圾回收机制

英文原文:cubrid,编译:ImportNew王晓杰 html

本文是成为Java GC专家系列文章的第三篇。在第一篇《成为JavaGC专家Part I — 深刻浅出Java垃圾回收机制》中咱们学习了不一样GC算法的执行过程,GC是如何工做的,什么是新生代和老年代,你应该了解的JDK7中的5种GC类型,以及这5种类型对于应用性能的影响。 java

在第二篇《成为JavaGC专家Part II — 如何监控Java垃圾回收机制》,我解释了JVM其实是如何执行垃圾回收的,咱们如何监控GC,以及那哪些具可让咱们的工做更快,更高效。在第三篇文章中,咱们会基于实际的例子来解释一些优化GC的最佳实践。我认为在阅读本篇文章以前,你已经很好地理解了以前的文章,所以,为了你可以更好地学习本文,若是你尚未读过以前的两篇文章话,请先阅读。 算法

为何须要优化GC 服务器

或者说的更确切一些,对于基于Java的服务,是否有必要优化GC应该说,对于全部的基于Java的服务,并不老是须要进行GC优化,但前提是所运行的基于Java的系统,包含了以下参数或行为: 并发

  • 已经经过 -Xms 和–Xmx 设置了内存大小
  • 包含了 -server 参数
  • 系统中没有超时日志等错误日志

换句话说,若是你没有设定内存的大小,而且系统充斥着大量的超时日志时,你就须要在你的系统中进行GC优化了。 oracle

可是,你须要时刻铭记一条GC优化永远是最后一项任务。 jvm

想一下进行GC优化的最根本缘由,垃圾收集器清除在Java程序中建立的对象,GC执行的次数即须要被垃圾收集器清理的对象个数,与建立对象的数量成正比,所以,首先你应该减小建立对象的数量ide

俗话说的好,“冰冻三尺非一日之寒”。咱们应该从小事作起,不然日积月累就会很难管理。 工具

  • 咱们须要使用StringBuilder 或者StringBuffer 来替代String
  • 应该尽可能少的输出日志

可是,咱们知道有些状况会让咱们一筹莫展,咱们眼睁睁的看着XML以及JSON解析占用了大量的内存。即使咱们已经尽量少的使用String以及尽可能少的输出日志,大量的临时内存被用于XML或者JSON解析,例如10-100MB。可是,舍弃XML和JSON是很难的。咱们只要知道,他会占用不少内存。 性能

若是应用内存使用量重复几回调整以后增长了,你就能够开始GC优化了。

我为GC优化概括了两个目的:

  1. 一个是将转移到老年代的对象数量降到最少
  2. 另外一个是减小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参数中与内存大小相关的,能够影响性能的参数。

1GC优化须要考虑的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)

2GC类型可选参数

分类

参数

备考

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类型是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.160.005.1863.7820.32542.04756.9468.993

最左边的Perm 空间对于最初的GC优化不是很重要,这一次YGC参数的值更加有用。

Minor GC和Full GC的平均值以下表所示

3Service SMinor 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.610.0030.6724.6222.38242430.21900.00030.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.0030.483.3126.5437.0122611.131411.75822.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秒以内。

2Case6的时间图表

基于以上结果。咱们按照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性能工程研究院

英文原文:cubrid,编译:ImportNew-王晓杰

译文地址: http://www.importnew.com/3146.html

相关文章
相关标签/搜索