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

澳门新葡亰3522平台游戏 7

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

ava
虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统

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

 

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

 澳门新葡亰3522平台游戏 1

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

JVM Heap 分两大块: 

默认配置下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

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

澳门新葡亰3522平台游戏 2

图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频发且持续时间较短。

 澳门新葡亰3522平台游戏 3

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

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

澳门新葡亰3522平台游戏 4

图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)

 

优化新生代内存大小

优化分代垃圾回收意味着让新生代,特别是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。使用现在的配置,重复上面的负载测试,我们得到了下面的结果:

澳门新葡亰3522平台游戏 5

图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拥有更高的服务器利用率。

 

避免老年代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应用优化垃圾回收(下)

         一块是 NEW Generation,另一块是 Old Generation. 在 NewGeneration
中,有一个叫 Eden 的空间,主要

是用来存放新生的对象,还有两个 Survivor
Spaces(from,to),它们的大小总是一样,它们用来存放每次

垃圾回收后存活下来的对象。在 OldGeneration
中,主要存放应用程序中生命周期长的内存对象。在

澳门新葡亰3522平台游戏,NewGeneration 块中,垃圾回收一般用 Copying 的算法,速度快。每次 GC
的时候,存活下来的对象首先

由 Eden 拷贝到某个 SurvivorSpace, 当 Survivor Space 空间满了后, 剩下的
live 对象就被直接拷贝到

OldGeneration 中去。因此,每次 GC 后,Eden 内存块会被清空。在
OldGeneration 块中,垃圾回收一般

用 mark-compact 的算法,速度慢些,但减少内存要求.

垃圾回收分多级,0 级为全部(Full)的垃圾回收,会回收 OLD 段中的垃圾;1
级或以上为部分垃圾回收,只

会回收 NEW 中的垃圾,内存溢出通常发生于 OLD 段或 Perm
段垃圾回收后,仍然无内存空间容纳新的 Java

对象的情况。

内存申请过程如下:

  1. JVM 会试图为相关 Java 对象在 Eden 中初始化一块内存区域

  2. 当 Eden 空间足够时,内存申请结束。否则到下一步

  3. JVM 试图释放在 Eden 中所有不活跃的对象(这属于 1
    或更高级的垃圾回收),释放后若 Eden 空

间仍然不足以放入新对象,则试图将部分 Eden 中活跃对象放入 Survivor 区

  1. Survivor 区被用来作为 Eden 及 OLD 的中间交换区域,当 OLD
    区空间足够时,Survivor 区的对

象会被移到 Old 区,否则会被保留在 Survivor 区

  1. 当 OLD 区空间不够时,JVM 会在 OLD 区进行完全的垃圾收集(0 级)

  2. 完全垃圾收集后,若 Survivor 及 OLD 区仍然无法存放从 Eden
    复制过来的部分对象,导致 JVM

无法在 Eden 区为新对象创建内存区域,则出现”out of memory 错误”

垃圾回收算法 按照基本回收策略分:复制、标记-清除算法、标记-压缩算法

复制算法(copying)

将内存分成两块,每次只使用其中一块,垃圾回收时,将标记的对象拷贝到另外一块中,然后完

全清除原来使用的那块内存。复制后的空间是连续的。复制算法适用于新生代,因为垃圾对象

多于存活对象,复制算法更高效。在新生代串行垃圾回收算法中,将 eden
中标记存活的对象拷

贝未使用的 s1 中,s0 中的年轻对象也进入 s1,如果 s1
空间已满,则进入老年代;这样交替

使用 s0 和
s1。这种改进的复制算法,既保证了空间的连续性,有避免了大量的内存空间浪费。

 澳门新葡亰3522平台游戏 6

 

标记-清除算法(Mark-Sweep)

从根节点开始标记所有可达对象,其余没标记的即为垃圾对象,执行清除。但回收后的空间是不

连续的。

标记-压缩算法(Mark-compact)

适合用于老年代的算法(存活对象多于垃圾对象)。

标记后不复制,而是将存活对象压缩到内存的一端,然后清理边界外的所有对象。 

 

 澳门新葡亰3522平台游戏 7

按分区对待的方式分:

增量收集(Incremental
Collecting):实时垃圾回收算法,即:在应用进行的同时进行垃

圾回收。不知道什么原因 JDK5.0 中的收集器没有使用这种算法的。

 分代收集(Generational
Collecting):基于对对象生命周期分析后得出的垃圾回收算法。

把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中

的一个)进行回收。现在的垃圾回收器(从 J2SE1.2 开始)都是使用此算法的。

 

VM 调优建议: