怎么样调整JVM GC?

怎么样调整JVM GC?


调整JVM ( v1.3.1) GC

关键词:

gcgarbage collection(垃圾收集)

infant mortality:对象分配以后很快成为垃圾,就称该对象具有infant mortality

minor collection:较小收集

major collection:较大收集

older generation:年老代

young generation:年轻代

footprint是一批工作进程的集合,以页和缓冲行数计量,在物理内存有限或者有很多处理器的系统里,footprint 可代表伸缩性

survivor spaces:生存空间

eden新的对象分配的地方

throughput:是未消耗在垃圾收集的时间占总时间的百分比

简介:

Java 2平台越来越多的应用于大型的服务器应用,web services。这类应用要求有可扩展性,并直接受益于多线程,多处理器,sockets以及内存。然而,“big iron”性能被誉为一种艺术形式,并需要特殊技术,这种技术超出改善小型系统性能所需要的技术。幸运的是,JVMSolaris操作环境提供了线程、I/O和内存管理的有利条件。这篇文档阐述了在获取高性能的过程中所遇到的难题:GC难调。

Amdahl发现大部分的工作不能被很好地并行化:某些工作总是串行的,但是并不能从并行化获得好处。Java2平台也是这种情况。特别是,JVM 1.3.1及以上版本没有并行GC,所以相对于并行收集的应用,在多处理器系统的GC的影响会增长。

下图显示一个完美的理想系统,除GC外,具有良好的伸缩性。最上面的线(红色),反映了在单处理器上,只花1%时间在GC上的应用情况:这可以理解为,在32个处理器上,将会损失至少20%的Throughput。到10%时,如果不考虑单处理器应用中GC所用大量时间,那么损失的Throughput将会超过75%。

怎么样调整JVM GC?

 

 

 

这就证明了当GC花费时间比例增大的时候,在小型系统应用上所损失的Throughput可能会成为瓶颈问题。唯一的希望就是,对这个瓶颈问题的一点小改进能获得很高的性能。对于一个大型的系统来讲,调整GC则是值得的。

这篇文档描述的是Solaris(SPARC Platform Edition)操作环境中的JVM 1.3.,因为这个平台提供了当今Java2平台最具伸缩性的软硬件环境。然而,这些描述的文字同样适用于其他的平台,包括Linux,Windows,Solaris(Intel Architecture)操作环境,以达到升级硬件的最大可用程度。尽管命令行选项适用于大部分的平台,但是一些平台可能有与这里所述不同的缺省值。

分代收集:

Java 2 平台一个很强的特性之一就是屏蔽内存分配和GC的复杂性。然而,一旦GC成为瓶颈,那么就要理解所隐藏实现的细节。垃圾收集器对应用使用对象的方式作了限定,这些限定就反映在可调整参数中。这些参数可以被调整,在不牺牲抽象能力情况下获取更高的性能

在一个运行的程序中,如果一个对象不再有任何引用,那么它将成为垃圾。大部分GC算法简单就是对每个可获取对象进行遍历:任何被遗弃的对象,将成为垃圾。这种算法所花的时间和实际活动对象的数量成比例,但对于具有大量活动数据的大型应用,就不可行了。

JVM v1.3 集许多不同的GC算法为一体,这些不同的算法是通过分代收集结合在一起的。当GCHeap中检查每一个活动的对象时,分代收集利用大多数应用的几个属性来避免额外的工作。

这些属性中,最重要的是infant mortality(对象分配以后很快成为垃圾。下图中蓝色区域显示了对象生命周期的典型分布。左边的峰值代表在分配之后能很快收集的对象。例如,重复对象(Iterator objects)在一个单循环期间,经常是活动的。

怎么样调整JVM GC?

一些对象存活时间越长,就越向右进行分布。例如,典型的例子是,一些在初始化时就被分配并一直存活到程序退出的对象。在这两个极端之间的是一些在中间计算中所存活的对象,就是这里那个峰值右边的区域。尽管一些应用有不同的分布情况,但大多数应用都符合这个通用图形。通过关注大多数对象的infant mortality进行有效的收集是可能的。

为此,内存是分代管理的:内存池对不同代中的对象进行管理。GC是在每代中内存池满的时候进行的:如上图中竖线所示。对象分配在Eden中,那是多数初期对象变成垃圾的地方。当Eden 填满时,将会引起minor collection,在其中的存活的对象将会移动到older generation中。当older generation需要去收集的时候,那就是major collection,通常会比较慢。因为它包含了所有活动的对象。

 

这图显示了一个调整好的系统,在该系统中,大多数对象在第一次的垃圾收集前就销毁掉了。一个对象活动时间越长,经历GC的次数就越多,GC速度就越慢。通过让大多数对象存活不到一次收集就销毁,可使GC变得十分有效。但是这种令人满意的情况,在具有不寻常的生命周期分布的应用中或造成收集频繁的大小不合适的代中就会被破坏。

默认的GC参数对大多数小型应用都是有效的。对于许多服务器应用,它们并不是最佳参数。这就引出了这篇文档的主旨:

如果GC成为瓶颈,你可以定制代的大校检查详细的GC输出,研究 GC 参数对性能的影响。
 

收集的类型:

每个分代有一个相关联的GC类型,这些类型的GC可以进行配置,产生不同的算法时间,空间,中止交易。在1.3中,JVM实现了三种不同的GC

1, Copying(有时,称为清扫):这个收集者可以有效的在两个或多个分代中进行对象的移动。原分代变空,可以将遗留的销毁对象进行回收。然而,需要空间去操作,并拷贝所需的footprint。在1.3.1,复制收集用于所有的minor collections.

2, Mark-compact:这个收集者允许分代在适当的时候进行分配,而不需要额外的内存。然后,这种紧凑的比复制方式,速度上要慢一些。在1.3.1中,紧凑标记的方式主要用于major collection.

3, Incremental(有时称为序列)。只有在命令行中设置了 -Xincgc之后,这种收集方式才起作用。借助于详细的记录,递增式的GC一次只能收集older generation的一部分,在多次minor collections之后,才尝试进行major collections。然而,如果考虑所有的Throughput的话,这种方式比紧凑标记的速度还要慢。

因此,复制方式是最快的,在收集时尽量使用这种方式来收集对象。

默认情况下的分代排列如下图所示:

怎么样调整JVM GC?

在进行初始化的时候,最大的地址空间只是事实上的设定,在实际需要的时候,才分配物理内存。全部的地址空间分成young generationolder generation

young generation包括Eden和两个survivor spaces。对象最初分配在Eden中。其中保证一个送survivor spaces在任何时候都是空的,当垃圾收集发生时, Eden中的活的对象复制到survivor spaces,对象就在survivor spaces之间复制,直到到达最大门限值(老化),然后复制到older generation
(其它的虚拟机,包括JVM 1.2版本 For Solaris,使用两个大小相等的空间来复制,而不是使用一个大的Eden加两个小空间)。这就是说定义young generation 参数,并不能直接可比较的。

older generation在合适的时候,使用Mark-compact方式进行收集。名为永久代选项比较特别,因为它保存包括JVM 自身的所有反映数据(reflective data),例如类以及方法。

性能指标

衡量GC性能有两个指标。Throughput是未消耗在垃圾收集的时间占总时间的百分比,Throughput包括花在分配上的时间(不需要调整分配的速度),停顿(Pauses)是应用因为垃圾收集而停止响应的时间。

用户对于垃圾收集有不同的需求,例如, 对于web服务器的主要尺度是Throughput,因为垃圾收集的停顿也许是不可容忍的,或者只是被网络延时所遮盖。然而,对于交互式图形程序,短暂的延迟也会影响用户的体验。

一些用户对于其他一些考虑敏感,Footprint是处理的工作区,用页面和cache line 作为尺度测量.在有限的物理内存或许多处理器的系统上,footprint 可以显示伸缩性.Promptness是对象死亡和内存可用之间的时间.另一个对于分布时系统比较重要的考量标准是远程方法调用(RMI)

一般来说,选择某个代大小时要平衡考虑各种考虑因素.例如,一个非常大的young generation也许会最大化throughput,但是以footprint,promptness为代价的。小的young generationincremental collection可以使停顿时间的减少,但是以牺牲Throughput为代价的。

没有一种正确的方式去衡量代的大小:最好的选择是由应用使用用户需要的内存。因此,JVM 默认的GC可能并不是最好的,可以由用户使用命令去覆盖。

测量方法

Throughputfootprint是最好的标准,最好使用对于应用来说特定的手段测量。例如,一个web serverThroughput用一个客户端的来测试,同时在Solaris操作系统上,服务器的footprint可以用pmap命令来衡量。换句话说,由于GC而停顿,很容易由于JVM自己的诊断输出来得到。

命令行的参数: -verbosegc