为任务关键型Java应用优化垃圾回收

澳门新葡亰娱乐官网 10

为任务关键型Java应用优化垃圾回收(上)

最近,我有机会去测试并优化几个基于Java购物和门户网站程序,它们运行在Sun/Oracle的JVM上。访问量最高的几个应用在德国。在很多情况下,垃圾回收是Java服务器性能的一个关键因素。本文中,我们会研究一些先进垃圾回收算法思想以及一些重要的调节参数。我们会在各种真实场景中比较这些参数。

澳门新葡亰娱乐官网 1

并行标记清除(CMS)回收器

CMS垃圾回收器是第一个广泛使用的低延迟回收器。虽然在Java
1.4.2中就可以使用了,但刚开始还不是很稳定。这些问题直到Java
5才得以解决。

从CMS回收器的名字就可以看出它使用并行方式:大部分回收工作由一个GC线程完成,与处理用户请求的工作线程并行执行。老年代原来单一的stop-the-world回收过程被划分为两个更短的stop-the-world暂停加上5个并行阶段。在这些并行阶段中,原来的工作线程照常运行(不会被暂停)。关于CMS更详细的介绍可以参考这篇文章《Java
SE 6 HotSpot Virtual Machine Garbage Collection
Tuning》。

使用下面的参数可以激活CMS回收器:

-XX:+UseConcMarkSweepGC

再次应用到上面的测试程序(并提高负载)可以得到以下结果:

澳门新葡亰娱乐官网 2

图4 优化堆大小并使用CMS的JVM在50小时内的GC行为(-Xms1200m -Xmx1200m
-XX:NewSize=400m -XX:MaxNewSize=400m -XX:SurvivorRatio=6
-XX:+UseConcMarkSweepGC))

可以看到,老年代GC的8s左右暂停已经消失了。现在,老年代回收过程只出现两次暂停(前一次的结果50小时内有5次),并且所有暂停都在1s内。

默认情况下,CMS回收器使用ParNew(GC算法)处理新生代回收。如果ParNew和CMS一起运行,它的暂停会比没有CMS时长一点,因为他们之间需要额外的协同工作。与上次的测试结果相比,可以从新生代的平均暂停时间略有上升发现这个问题。新生代暂停时间中离群值频繁出现,从这里也可以发现这个问题。离群值可以达到0.5s左右。但是这些暂停对很多应用来说已经足够短了,所以CMS/ParNew组合可以作为一个很好的低延迟优化选择。

CMS回收器的一个严重缺陷就是,当老年代空间都被占满时CMS无法启动。一旦老年代被占满了,启动CMS就太晚了;虚拟机必须使用通常的“stop-the-world”策略(在GC日志中会出现“concurrent
mode
failure”的记录)。为了实现低延迟目标,当老年代空间占用量达到一定门限值时,就应该启动CMS回收器,通过以下设置来实现:

-XX:CMSInitiatingOccupancyFraction=80

这表示一旦老年代空间被占用80%时,CMS回收器就会运行。对于我们的应用,使用这个值(也就是默认值)就可以。但如果把门限值设太高的话,就会产生“concurrent
mode
failure”,导致长时间的老年代GC暂停。反过来,如果设的太低(低于活跃空间大小),CMS可能一直并行运行,导致某个CPU核心完全用在GC上。如果一个应用的对象创建和堆使用行为变化很快,比如通过交互的方式或者计时器启动专门的任务,很难设置一个合适的门限值同时避免上述两种问题。

从垃圾回收的角度来看,Java服务器程序可以有广泛多样的需求:

欢迎访问我的博客查看原文:http://wangnan.tech

碎片的阴影

然而,CMS最大的一个问题是它不会整理老年代堆空间。这样会产生堆碎片,随着时间运行,会导致服务严重恶化。有两个因素会导致这种情况:紧缺的老年代空间大小,以及频繁的CMS回收。第一个因素可以通过增大老年代堆空间来改善,要大于ParallelGC回收器所需要的空间(我从1024M增加到1200M,从前几幅图可以看到)。第二个问题可以通过合适地划分各代空间来优化,前面讲过。我们可以实际看一下这样可以把老年代GC的频率降低多少。

为了证明使用CMS前合理地调整各代堆大小很重要,我们先看看如果不遵守上述的原则,在图1(几乎不对堆做优化)的基础上直接使用CMS回收器会怎么样:

澳门新葡亰娱乐官网 3

图5
未优化堆大小的GC行为,以及使用CMS后内存碎片导致的性能恶化(从第14小时开始)

很明显,JVM在这样设置的负载测试下可以稳定地工作将近14个小时(在生产环境以及更小的负载条件下,这个不稳定的良性阶段可能会持续更久)。接下来,突然间会出现多次很长的GC暂停,暂停时间几乎占剩余时间的一半。不仅老年代的暂停时间会达到10s以上,而且新生代的暂停时间也会达到数秒。因为回收器为了将新生代的对象移到老年代,需要耗费很长的时间搜索老年代空间。

CMS低延迟优点的代价就是内存碎片。这个问题可以最小化,但是不会彻底消失。你永远不知道它什么时候会被触发。然而,通过合理的优化与监控可以控制它的风险。

  1. 一些高流量应用需要响应大量请求并创建非常多的对象。有时候,一些使用了高资源消耗框架的中等流量应用也会遇到同样的问题。总之,对垃圾回收来说,如何有效地清理这些生成的对象是一个很大的挑战。
  2. 另外,一些应用需要长时间运行并且在运行过程提供稳定的服务,要求性能不会随着时间而慢慢变差或者突然恶化。
  3. 某些场景需要严格限制用户响应时间(比如网络游戏或者投注应用等),几乎不允许额外的GC暂停。

说明

针对jdk7

G1(Garbage First)回收器的希望

G1回收器设计的目的就是保证低延迟的同时而没有堆碎片风险。因此,Oracle把它作为CMS的一个长期取代。G1可以避免碎片风险是因为它会整理堆空间。对于GC暂停来说,G1的目标并不是使暂停时间最小化,而是设置一个时间上限,使GC暂停尽量满足这一上限值。读者可以从G1的重要教程《Getting
Started with the G1 Garbage
Collector》中了解到更详细的内容。德国的读者可以也阅读Angelika
Langer的文章《Der Garbage-First Garbage Collector (G1) – Übersicht über
die
Funktionalität》。

在将G1回收器用于测试程序中并与上述其他经典回收器做对比之前,先总结两点关于G1的重要信息。

  • Oracle在Java 7u4中开始支持G1。为了使用G1你应该将Java
    7更新到最新。Oracle的GC团队一直致力于G1的研发,在最新的Java更新中(本文编写时最新版本是7u7到7u9),G1的改进很显著。另一方面,G1无法在任何Java
    6版本中使用,而且到目前更优越的Java 7不可能向后移植到Java 6中。
  • 前面关于调节各代空间大小的优化对G1来说已经淘汰了。设置各代空间大小与设置暂停目标时间相冲突会使G1回收器偏离原本的设计目标。使用G1时,可以使用“-Xms”和“-Xmx”设置整体的内存大小,也可以设置GC暂停目标时间(可选),对G1来说不用设置其他选项。与ParallelGC回收器的AdapativeSizingPolicy类似,它自适应地调整各代空间大小来满足暂停目标时间。

遵循这些原则后,G1回收器在默认配置下的结果如下:

澳门新葡亰娱乐官网 4

图6 最小配置(-Xms1024m -Xmx1024 -XX:+UseG1GC)的JVM在G1下26小时内的GC性能

在这个例子中,我们使用了默认的GC暂停目标时间200ms。从图中可以看到,平均时间与这个目标比较吻合,最长GC暂停时间与使用CMS回收器差不多(图4)。G1明显可以很好地控制GC暂停,与平均时长相比,离群值也相当少。

另一方面,平均GC暂停时间要比CMS回收器长很多(270 vs
100ms),而且更频繁。这意味着GC累积暂停时间(也就是GC本身所占总时间)是使用CMS的4倍以上(6.96%
vs 1.66%)。

与CMS一样,G1也分为GC暂停阶段和并行回收阶段(不暂停任务)。同样与CMS类似,当堆占用比达到一定门限后,它才启动并行回收阶段。从图6可以看到,1GB的可用内存到目前为止并没有完全使用。这是因为G1的默认占用比门限值要比CMS低很多。也有人指出,一般来说较小的堆空间就可以满足G1的需求。

在很多场景中,你可以通过不同的优先级将几种需求结合起来。我的几个样例程序对第一点要求比第二点要高很多,但是绝大部分程序不会同时对这三方面要求都高。这给你留下了足够权衡的空间。

关于垃圾回收

可以查看我之前的博文:
JVM内存管理-垃圾回收与内存分配

垃圾回收器的定量比较

下面的表格总结了Oracle Java
7中4种最重要的垃圾回收器在测试中的关键性能指标。在同样的应用程序上,进行相同的负载测试,但是负载的级别不同(由第2列的垃圾创建速率体现)。

澳门新葡亰娱乐官网 5

表 几种垃圾回收器的比较

所有的回收器都运行在1GB的堆空间上。传统的回收器(ParallelGC、ParNewGC和CMS)另外使用下面的堆设置:

-XX:NewSize=400m -XX:MaxNewSize=400m -XX:SurvivorRatio=6

而G1回收器没有额外的堆大小设置,并且使用默认的暂停目标时间200ms,也可以显示设置:

-XX:MaxGCPauseMillis=200

从表中可以看到,传统回收器在新生代回收上(第3列)时间差不多。对ParallelGC和ParNewGC来说是差不多的,而CMS实际上也是使用ParNewGC去回收新生代。然而,在新生代GC暂停中,将新生代存活对象移入老年代需要ParNewGC和CMS的协同。这样的协同引入额外的代价,也就导致CMS的新生代GC暂停时间要略长。

第7列是GC暂停所耗费的时间占总时间的百分比,这个值可以很好地反映GC的总时间代价。因为并行GC总时间(最后一列)以及引入的CPU占用代价可以忽略。按前文所述,优化堆大小后老年代GC次数会变得很少,这样第7列的值主要由新生代GC暂停总时间所决定。新生代暂停总时间是新生代暂停(连续)时长(第3列)与暂停次数的乘积。新生代暂停频率与新生代空间大小有关,对传统回收器来说,这个大小是相同的(400MB)。因此,对传统回收器来说,第7列的值或多或少地反映着第3列的值(负载差不多的情况)。

CMS的优点可以从第6列明显看出:它用稍长的总时间代价换来了更短(低一个量级)的老年代GC暂停。对很多真实环境的应用来说,这是一个不错的折衷。

那么,对于我们的应用,G1回收器表现怎么样呢?第6列(以及第5列)可以看出,在减少老年代GC暂停时长上,G1回收器要比CMS回收器做的好。但是从第7列也可以看到,它付出相当高的代价:同样的负载下,GC总时间代价占7%,而CMS只占1.6%。

我会在后续的文章中检查在什么条件下会导致G1产生更高的GC时间代价,同样也会分析G1与其他回收器(尤其是CMS回收器)相比的优缺点。这是一个庞大而且有价值的主题。

默认配置下JVM GC的性能

JVM有很多改进,但仍然不能在程序运行时对任务做优化。除了上面提到的三点,默认的JVM设置还有一个优先级仅次于它们的需求:减小内存占用。考虑到成千上万的用户并不是在内存充足的服务器上运行。对很多电子商务产品也很重要,因为这些应用大部分时间被配置在开发笔记本上运行,而不是在商用服务器上。因此,如果你的服务器配置着最小的堆空间和GC参数,比如下面这样配置,

java -Xmx1024m -XX:MaxPermSize=256m -cp Portal.jar my.portal.Portal

这样肯定会导致系统运行不够高效。首先,好的做法不仅配置内存最大限制,也需要配置初始内存大小,以避免服务器在启动过程中逐步增加内存。否则代价会很大。当知道服务器需要多少内存时(你应该及时地查明),最好将初始内存大小与最大内存设置相等。可以通过以下JVM参数来设置:

-Xms1024m -XX:PermSize=256m

最后一个经常在JVM配置的基本选项是配置新生代堆内存大小,与上面设置的方式类似:

-XX:NewSize=200m -XX:MaxNewSize=200m

下面的章节会对上面的配置以及更复杂的配置给出解释。首先,让我们看一个门户网站的应用,它运行在一台相当慢的测试主机上。当进行负载测试时,它的垃圾回收是怎么工作的:

澳门新葡亰娱乐官网 6

图1 堆大小稍微优化后的JVM在25小时左右的GC行为(-Xms1024m -Xmx1024m
-XX:NewSize=200m -XX:MaxNewSize=200m)

其中,蓝色的曲线表示总的堆内存占用量随时间的变化,垂直的灰色线条表示GC暂停的间隔。

除了曲线图,GC操作的关键指标和性能显示在最右边。首先我们看一下在这次测试中,垃圾被创建(和回收)的平均量。30.5MB/s的数值被标为黄色,因为这是一个相当大但还可以的垃圾生成速率,对一个引导性的GC调优例子而言还算可以。其他值表示JVM在清理这些垃圾时的表现:99.55%的垃圾是在新生代中被清理的,老年代的只占0.45%。这个结果相当不错,因此标为绿色。

之所以有这样的结果,可以从GC引入的暂停间隔看出来(以及处理用户请求的工作线程):有很多但很短暂的新生代GC间隔,平均每6s一次,持续时间不会超过50ms。这些暂停使JVM停止运行的时间占总时间的0.77%,但是每次暂停对等待服务器响应的用户来说完全感觉不到。

澳门新葡亰娱乐官网 ,另一方面,老年代GC的暂停只占总时间的0.19%。但是,在这段时间内老年代GC只清理了0.45%的垃圾,而新生代GC用占0.77%的时间清理了99.55%的垃圾。可见,与新生代GC相比,老年代GC是多么低效。另外,老年代GC的暂停平均触发速率不到一个小时一次,但平均持续时间可达到8s,最大异常值甚至达到19s。由于这些暂停会真正地停止JVM处理用户请求的线程,因此暂停应尽量不频发且持续时间短。

通过以上观察可以得出分代垃圾回收的基本调优目标:

  • 新生代GC尽量回收多的垃圾,避免老年代GC频发且持续时间较短。

垃圾收集器分类

  • 按线程数分

可以分为串行垃圾回收器和并行垃圾回收器。串行垃圾回收器一次只使用一个线程进行垃圾回收;并行垃圾回收器一次将开启多个线程同时进行垃圾回收。在并行能力较强的
CPU 上,使用并行垃圾回收器可以缩短 GC 的停顿时间。

  • 按照工作模式分

可以分为并发式垃圾回收器和独占式垃圾回收器。并发式垃圾回收器与应用程序线程交替工作,以尽可能减少应用程序的停顿时间;独占式垃圾回收器
(Stop the world)
一旦运行,就停止应用程序中的其他所有线程,直到垃圾回收过程完全结束

  • 按碎片处理方式

可分为压缩式垃圾回收器和非压缩式垃圾回收器。压缩式垃圾回收器会在回收完成后,对存活对象进行压缩整理,消除回收后的碎片;非压缩式的垃圾回收器不进行这步操作。

  • 按工作的内存区间

可分为新生代垃圾回收器和老年代垃圾回收器

总结与展望

对所有的经典Java
GC算法(SerialGC、ParallelGC、ParNewGC和CMS)来说,优化各代堆空间大小是很重要的,然而实际中很多应用程序并没有做足够合理的优化。导致的结果就是应用性能不够优化,以及操作退化(造成性能损失,如果没有很好地监控甚至会出现一段时间内程序暂停的情况)。

优化各代堆空间大小可以显著提高应用性能,并将GC长暂停次数减到最小。然后,消除GC长暂停需要使用低延迟回收器。CMS一直(直到现在)是首选且有效的低延迟回收器。在很多情况下,CMS就可以满足需求。通过合理的优化,它还是可以保证长期稳定,只不过存在堆碎片的风险。

作为替代,G1回收器目前(Java
7u9)是一个被支持且可用的选择,但仍有改进的余地。对很多应用来说,它的结果可以接受,但与CMS回收器相比还不是很好。其优缺点的细节值得仔细地研究。

分代垃圾回收的基本思想与堆内存大小调整

先从下图开始。这个图可以通过JDK工具得到,比如jstat或者jvisualvm以及它的visualgc插件:

澳门新葡亰娱乐官网 7

图2 JVM的堆内存结构,包括新生代的子分区(最左列)

Java的堆内存由永久代(Perm),老年代(Old)和新生代(New or
Young)组成。新生代进一步划分为一个Eden空间和两个Survivor空间S0、S1。Eden空间是对象被创建时的地方,经过几轮新生代GC后,他们有可能被存放在Survivor空间。如果想了解更多,可以读一下Sun/Oracle的白皮书Memory
Management in the Java HotSpot Virtual
Machine

默认情况下,作为整体的新生代特别是Survivor空间太小,导致在GC清理大部分内存之前就无法保存更多对象。因此,这些对象被过早地保存在老年代中,这会导致老年代被迅速填满,必须频繁地清理垃圾。这也是图1中产生较多的Full
GC暂停的原因。

(译者注:一般新生代的垃圾回收也称为Minor GC,老年代的垃圾回收称为Major
GC或Full GC)

垃圾收集器的指标

  • 吞吐量

指在应用程序的生命周期内,应用程序所花费的时间和系统总运行时间的比值。系统总运行时间=应用程序耗时+GC
耗时。如果系统运行了 100min,GC 耗时 1min,那么系统的吞吐量就是
(100-1)/100=99%。
垃圾回收器负载:和吞吐量相反,垃圾回收器负载指来记回收器耗时与系统运行总时间的比值。

  • 停顿时间

指垃圾回收器正在运行时,应用程序的暂停时间。对于独占回收器而言,停顿时间可能会比较长。使用并发的回收器时,由于垃圾回收器和应用程序交替运行,程序的停顿时间会变短,但是,由于其效率很可能不如独占垃圾回收器,故系统的吞吐量可能会较低。

  • 垃圾回收频率

指垃圾回收器多长时间会运行一次。一般来说,对于固定的应用而言,垃圾回收器的频率应该是越低越好。通常增大堆空间可以有效降低垃圾回收发生的频率,但是可能会增加回收产生的停顿时间。

  • 反应时间

指当一个对象被称为垃圾后多长时间内,它所占据的内存空间会被释放。

  • 堆分配

不同的垃圾回收器对堆内存的分配方式可能是不同的。一个良好的垃圾收集器应该有一个合理的堆内存区间划分。

优化新生代内存大小

优化分代垃圾回收意味着让新生代,特别是Survivor空间,比默认情形大。但是同时也要考虑虚拟机使用的具体GC算法。

当前硬件上运行的Sun/Oracle虚拟机使用了ParallelGC作为默认GC算法。如果使用的不是默认算法,可以通过显式配置JVM参数来实现:

-XX:+UseParallelGC

默认情况下,这个算法并不在固定大小的Eden和Survivor空间中运行。它使用了一种自适应调整大小的策略,称为“AdaptiveSizePolicy”策略。正如描述的那样,它可以适应很多场景,包括服务器以外的机器的使用。但在服务器上运行时,这并不是最优策略。为了可以显式地设置固定的Survivor空间大小,可以通过以下JVM参数关闭它:

-XX:-UseAdaptiveSizePolicy

一旦这么设置后,就不能进一步增加新生代空间的大小,但我们可以有效地为Survivor空间设置合适的大小:

-XX:NewSize=400m -XX:MaxNewSize=400m -XX:SurvivorRatio=6

“SurvivorRatio=6”表示Survivor空间是Eden空间的1/6或者是整个新生代空间的1/8,在这个例子中就是50MB,而自适应大小策略经常运行在非常小的空间上,大约只有几MB。使用现在的配置,重复上面的负载测试,我们得到了下面的结果:

澳门新葡亰娱乐官网 8

图3 堆内存优化后的JVM在50小时内的GC行为(-Xms1024m -Xmx1024m
-XX:NewSize=400m -XX:MaxNewSize=400m -XX:-UseAdapativeSizePolicy
-XX:SurvivorRatio=6)

这次的测试时间是上次的两倍,而垃圾的平均创建速率和之前基本一致(30.2MB/s,之前是30.5MB/s)。然而,整个过程只有两次老年代(Full)GC暂停,25小时左右才发生一次。这是因为老年代垃圾死亡速率(所谓的promation
rate)从137kB/s减小到了6kB/s,老年代的垃圾回收只占整体的0.02%。同时新生代GC的暂停持续时间仅仅从平均48ms增加到57ms,两次暂停的间隔从6s增长到10s。总之,关闭了自适应大小调整,合理地优化堆内存大小,使GC暂停占总时间的比例从0.95%减小到0.59%,这是一个非常棒的结果。

优化后,使用ParNew算法作为默认ParallelGC的替代,也能得到相似的结果。这个算法是为了与CMS算法兼容而开发的,可以通过JVM参数来配置-XX:+UseParNewGC。关于CMS下面会提到。这个算法不使用自适应大小策略,可以运行在固定Survivor大小的空间上。因此,即使使用默认的配置SurvivorRatio=8,也比ParallelGC拥有更高的服务器利用率。

垃圾收集器介绍

澳门新葡亰娱乐官网 9

避免老年代GC的长时间暂停

上述结果的最后一个问题就是,老年代GC的长时间暂停平均为8s左右。通过适当的优化,老年代GC暂停已经很少了,但是一旦触发,对用户来说还是很烦人的。因为在暂停期间,JVM不能执行工作线程。在我们的例子中,8s的长度是由低速老旧的测试机导致的,在现代硬件上速度能快3倍左右。另一方面,现在的应用一般使用1G以上的堆内存,可以容纳更多的对象。当前的网络应用使用的堆内存能达到64GB,(至少)需要一半的内存来保存存活的对象。在这种情况下,8s对老年代暂停来说是很短的。这些应用中的老年代GC可以很随意地就接近1分钟,对于交互式网络应用来说是绝对不能接受的。

缓解这个问题的一个选择就是采用并行的方式处理老年代GC。默认情况下,在Java
6中,ParallelGC和ParNew
GC算法使用多个GC线程来处理新生代GC,而老年代GC是单线程的。以ParallelGC回收器为例,可以在使用时添加以下参数:

-XX:+UseParallelOldGC

从Java
7开始,这个选项和-XX:+UseParallelGC默认被激活。但是,即使你的系统是4核或8核,也不要期望性能可以提高2倍以上。通常的结果会比2被小一些。在某些例子中,比如上述例子中的8s,这种提高还是比较有效的。但在很多极端的例子中,这还远远不够。解决方法是使用低延迟GC算法。

下篇中会讨论CMS(The Concurrent Mark and Sweep
Collector)、幽灵般的碎片、G1(Garbage
First)垃圾收集器和垃圾收集器的量化比较,最后给出总结。

为任务关键型Java应用优化垃圾回收(下)

新生代串行收集器

串行收集器主要有两个特点:第一,它仅仅使用单线程进行垃圾回收;第二,它独占式的垃圾回收。

在串行收集器进行垃圾回收时,Java
应用程序中的线程都需要暂停,等待垃圾回收的完成,这样给用户体验造成较差效果。虽然如此,串行收集器却是一个成熟、经过长时间生产环境考验的极为高效的收集器。新生代串行处理器使用复制算法,实现相对简单,逻辑处理特别高效,且没有线程切换的开销。在诸如单
CPU
处理器或者较小的应用内存等硬件平台不是特别优越的场合,它的性能表现可以超过并行回收器和并发回收器。在
HotSpot 虚拟机中,使用-XX:+UseSerialGC
参数可以指定使用新生代串行收集器和老年代串行收集器。当 JVM 在 Client
模式下运行时,它是默认的垃圾收集器。

老年代串行收集器

老年代串行收集器使用的是标记-压缩算法。和新生代串行收集器一样,它也是一个串行的、独占式的垃圾回收器。由于老年代垃圾回收通常会使用比新生代垃圾回收更长的时间,因此,在堆空间较大的应用程序中,一旦老年代串行收集器启动,应用程序很可能会因此停顿几秒甚至更长时间。虽然如此,老年代串行回收器可以和多种新生代回收器配合使用,同时它也可以作为
CMS
回收器的备用回收器。若要启用老年代串行回收器,可以尝试使用以下参数:-XX:+UseSerialGC:
新生代、老年代都使用串行回收器。

如果使用-XX:+UseParNewGC
参数设置,表示新生代使用并行收集器,老年代使用串行收集器

如果使用-XX:+UseParallelGC
参数设置,表示新生代和老年代均使用并行回收收集器

并行收集器

并行收集器是工作在新生代的垃圾收集器,它只简单地将串行回收器多线程化。它的回收策略、算法以及参数和串行回收器一样。

并行回收器也是独占式的回收器,在收集过程中,应用程序会全部暂停。但由于并行回收器使用多线程进行垃圾回收,因此,在并发能力比较强的
CPU 上,它产生的停顿时间要短于串行回收器,而在单 CPU
或者并发能力较弱的系统中,并行回收器的效果不会比串行回收器好,由于多线程的压力,它的实际表现很可能比串行回收器差。

开启并行回收器可以使用参数-XX:+UseParNewGC,该参数设置新生代使用并行收集器,老年代使用串行收集器。

设置参数-XX:+UseConcMarkSweepGC 可以要求新生代使用并行收集器,老年代使用
CMS。

并行收集器工作时的线程数量可以使用-XX:ParallelGCThreads
参数指定。一般,最好与 CPU 数量相当,避免过多的线程数影响垃圾收集性能

新生代并行回收 (Parallel Scavenge) 收集器

新生代并行回收收集器也是使用复制算法的收集器。从表面上看,它和并行收集器一样都是多线程、独占式的收集器。但是,并行回收收集器有一个重要的特点:它非常关注系统的吞吐量。

新生代并行回收收集器可以使用以下参数启用:

-XX:+UseParallelGC:新生代使用并行回收收集器,老年代使用串行收集器。

-XX:+UseParallelOldGC:新生代和老年代都是用并行回收收集器。

新生代并行回收收集器可以使用以下参数启用:

-XX:+MaxGCPauseMills:设置最大垃圾收集停顿时间,它的值是一个大于 0
的整数。收集器在工作时会调整 Java
堆大小或者其他一些参数,尽可能地把停顿时间控制在 MaxGCPauseMills
以内。如果希望减少停顿时间,而把这个值设置得很小,为了达到预期的停顿时间,JVM
可能会使用一个较小的堆
(一个小堆比一个大堆回收快),而这将导致垃圾回收变得很频繁,从而增加了垃圾回收总时间,降低了吞吐量。

-XX:+GCTimeRatio:设置吞吐量大小,它的值是一个 0-100 之间的整数。假设
GCTimeRatio 的值为 n,那么系统将花费不超过 1/(1+n)
的时间用于垃圾收集。比如 GCTimeRatio 等于
19,则系统用于垃圾收集的时间不超过 1/(1+19)=5%。默认情况下,它的取值是
99,即不超过 1%的时间用于垃圾收集。

除此之外,并行回收收集器与并行收集器另一个不同之处在于,它支持一种自适应的
GC 调节策略,使用-XX:+UseAdaptiveSizePolicy 可以打开自适应 GC
策略。在这种模式下,新生代的大小、eden 和 survivor
的比例、晋升老年代的对象年龄等参数会被自动调整,以达到在堆大小、吞吐量和停顿时间之间的平衡点。在手工调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标的吞吐量
(GCTimeRatio) 和停顿时间 (MaxGCPauseMills),让虚拟机自己完成调优工作

老年代并行回收收集器

老年代的并行回收收集器也是一种多线程并发的收集器。和新生代并行回收收集器一样,它也是一种关注吞吐量的收集器。老年代并行回收收集器使用标记-压缩算法,JDK1.6
之后开始启用。

使用-XX:+UseParallelOldGC
可以在新生代和老生代都使用并行回收收集器,这是一对非常关注吞吐量的垃圾收集器组合,在对吞吐量敏感的系统中,可以考虑使用。参数-XX:ParallelGCThreads
也可以用于设置垃圾回收时的线程数量。

CMS 收集器

与并行回收收集器不同,CMS 收集器主要关注于系统停顿时间。CMS 是
Concurrent Mark Sweep
的缩写,意为并发标记清除,从名称上可以得知,它使用的是标记-清除算法,同时它又是一个使用多线程并发回收的垃圾收集器。

CMS
工作时,主要步骤有:初始标记、并发标记、重新标记、并发清除和并发重置。其中初始标记和重新标记是独占系统资源的,而并发标记、并发清除和并发重置是可以和用户线程一起执行的。因此,从整体上来说,CMS
收集不是独占式的,它可以在应用程序运行过程中进行垃圾回收。

根据标记-清除算法,初始标记、并发标记和重新标记都是为了标记出需要回收的对象。并发清理则是在标记完成后,正式回收垃圾对象;并发重置是指在垃圾回收完成后,重新初始化
CMS
数据结构和数据,为下一次垃圾回收做好准备。并发标记、并发清理和并发重置都是可以和应用程序线程一起执行的。

CMS
收集器在其主要的工作阶段虽然没有暴力地彻底暂停应用程序线程,但是由于它和应用程序线程并发执行,相互抢占
CPU,所以在 CMS 执行期内对应用程序吞吐量造成一定影响。CMS
默认启动的线程数是 (ParallelGCThreads+3)/4),ParallelGCThreads
是新生代并行收集器的线程数,也可以通过-XX:ParallelCMSThreads
参数手工设定 CMS 的线程数量。当 CPU 资源比较紧张时,受到 CMS
收集器线程的影响,应用程序的性能在垃圾回收阶段可能会非常糟糕。

由于 CMS 收集器不是独占式的回收器,在 CMS
回收过程中,应用程序仍然在不停地工作。在应用程序工作过程中,又会不断地产生垃圾。这些新生成的垃圾在当前
CMS 回收过程中是无法清除的。同时,因为应用程序没有中断,所以在 CMS
回收过程中,还应该确保应用程序有足够的内存可用。因此,CMS
收集器不会等待堆内存饱和时才进行垃圾回收,而是当前堆内存使用率达到某一阈值时,便开始进行回收,以确保应用程序在
CMS 工作过程中依然有足够的空间支持应用程序运行。

这个回收阈值可以使用-XX:CMSInitiatingOccupancyFraction 来指定,默认是
68。即当老年代的空间使用率达到 68%时,会执行一次 CMS
回收。如果应用程序的内存使用率增长很快,在 CMS
的执行过程中,已经出现了内存不足的情况,此时,CMS 回收将会失败,JVM
将启动老年代串行收集器进行垃圾回收。如果这样,应用程序将完全中断,直到垃圾收集完成,这时,应用程序的停顿时间可能很长。因此,根据应用程序的特点,可以对-XX:CMSInitiatingOccupancyFraction
进行调优。如果内存增长缓慢,则可以设置一个稍大的值,大的阈值可以有效降低
CMS
的触发频率,减少老年代回收的次数可以较为明显地改善应用程序性能。反之,如果应用程序内存使用率增长很快,则应该降低这个阈值,以避免频繁触发老年代串行收集器。

标记-清除算法将会造成大量内存碎片,离散的可用空间无法分配较大的对象。在这种情况下,即使堆内存仍然有较大的剩余空间,也可能会被迫进行一次垃圾回收,以换取一块可用的连续内存,这种现象对系统性能是相当不利的,为了解决这个问题,CMS
收集器还提供了几个用于内存压缩整理的算法。

-XX:+UseCMSCompactAtFullCollection 参数可以使 CMS
在垃圾收集完成后,进行一次内存碎片整理。内存碎片的整理并不是并发进行的。-XX:CMSFullGCsBeforeCompaction
参数可以用于设定进行多少次 CMS 回收后,进行一次内存压缩。

-XX:CMSInitiatingOccupancyFraction 设置为
100,同时设置-XX:+UseCMSCompactAtFullCollection
和-XX:CMSFullGCsBeforeCompaction,日志输出如下:

关于CMS收集器,深入了解阅读下面的博文
link

G1 收集器 (Garbage First)

G1
收集器的目标是作为一款服务器的垃圾收集器,因此,它在吞吐量和停顿控制上,预期要优于
CMS 收集器。

与 CMS 收集器相比,G1
收集器是基于标记-压缩算法的。因此,它不会产生空间碎片,也没有必要在收集完成后,进行一次独占式的碎片整理工作。G1
收集器还可以进行非常精确的停顿控制。它可以让开发人员指定当停顿时长为 M
时,垃圾回收时间不超过 N。使用参数-XX:+UnlockExperimentalVMOptions
–XX:+UseG1GC 来启用 G1 回收器,设置 G1
回收器的目标停顿时间:-XX:MaxGCPauseMills=20,-XX:GCPauseIntervalMills=200。

关于G1收集器,深入了解阅读下面的博文
link
link

GC 相关参数总结

  1. 与串行回收器相关的参数
    -XX:+UseSerialGC:在新生代和老年代使用串行回收器。
    -XX:+SurvivorRatio:设置 eden 区大小和 survivor 区大小的比例。
    -XX:+PretenureSizeThreshold:设置大对象直接进入老年代的阈值。当对象的大小超过这个值时,将直接在老年代分配。
    -XX:MaxTenuringThreshold:设置对象进入老年代的年龄的最大值。每一次
    Minor GC 后,对象年龄就加
    1。任何大于这个年龄的对象,一定会进入老年代。
  2. 与并行 GC 相关的参数
    -XX:+UseParNewGC: 在新生代使用并行收集器。
    -XX:+UseParallelOldGC: 老年代使用并行回收收集器。
    -XX:ParallelGCThreads:设置用于垃圾回收的线程数。通常情况下可以和
    CPU 数量相等。但在 CPU
    数量比较多的情况下,设置相对较小的数值也是合理的。
    -XX:MaxGCPauseMills:设置最大垃圾收集停顿时间。它的值是一个大于 0
    的整数。收集器在工作时,会调整 Java
    堆大小或者其他一些参数,尽可能地把停顿时间控制在 MaxGCPauseMills
    以内。
    -XX:GCTimeRatio:设置吞吐量大小,它的值是一个 0-100 之间的整数。假设
    GCTimeRatio 的值为 n,那么系统将花费不超过 1/(1+n)
    的时间用于垃圾收集。
    -XX:+UseAdaptiveSizePolicy:打开自适应 GC
    策略。在这种模式下,新生代的大小,eden 和 survivor
    的比例、晋升老年代的对象年龄等参数会被自动调整,以达到在堆大小、吞吐量和停顿时间之间的平衡点。
  3. 与 CMS 回收器相关的参数
    -XX:+UseConcMarkSweepGC: 新生代使用并行收集器,老年代使用
    CMS+串行收集器。
    -XX:+ParallelCMSThreads: 设定 CMS 的线程数量。
    -XX:+CMSInitiatingOccupancyFraction:设置 CMS
    收集器在老年代空间被使用多少后触发,默认为 68%。
    -XX:+UseFullGCsBeforeCompaction:设定进行多少次 CMS
    垃圾回收后,进行一次内存压缩。
    -XX:+CMSClassUnloadingEnabled:允许对类元数据进行回收。
    -XX:+CMSParallelRemarkEndable:启用并行重标记。
    -XX:CMSInitatingPermOccupancyFraction:当永久区占用率达到这一百分比后,启动
    CMS 回收 (前提是-XX:+CMSClassUnloadingEnabled 激活了)。
    -XX:UseCMSInitatingOccupancyOnly:表示只在到达阈值的时候,才进行 CMS
    回收。
    -XX:+CMSIncrementalMode:使用增量模式,比较适合单 CPU。
  4. 与 G1 回收器相关的参数
    -XX:+UseG1GC:使用 G1 回收器。
    -XX:+UnlockExperimentalVMOptions:允许使用实验性参数。
    -XX:+MaxGCPauseMills:设置最大垃圾收集停顿时间。
    -XX:+GCPauseIntervalMills:设置停顿间隔时间。
  5. 其他参数
    -XX:+DisableExplicitGC: 禁用显示 GC。

(本文完)
参考:

  • https://www.ibm.com/developerworks/cn/java/j-lo-JVMGarbageCollection/
  • https://github.com/pzxwhc/MineKnowContainer/issues/89
  • https://github.com/pzxwhc/MineKnowContainer/issues/90
  • http://ifeve.com/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3g1%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8/
  • http://chenchendefeng.iteye.com/blog/455883
  • http://www.jianshu.com/p/50d5c88b272d

欢迎关注我的微信订阅号:

澳门新葡亰娱乐官网 10

欢迎关注我的开发者头条独家号搜索:269166

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图