谁需要GC调优 小规模程序,垃圾收集算法能很好的工作。因为里面的对象图不大,所以收集代价不高。但是如果是大规模的程序,对象成百上千,一次遍历,就算是复制收集器中只对活动对象的遍历,都需要很长的时间。所以,大规模程序,有必要深入了解GC的工作方式,了解和调整GC的参数。 关于串行和并行GC收集算法 在JVM 1.3.1以前,只有串行收集器,没有并行收集器,对于多处理器,系统吞吐量损失很大。见下图。为此,1.3.1以后(不包含1.3.1),引入了并发GC收集算法。 JVM1.4.2上,有4种垃圾收集器,默认会选择串行收集器。 JVM5.0后,则会根据用户机器的类型自动选择收集器。 
1% GC 中的 1%是指,在一个CPU上执行且只花1%的CPU时间做垃圾收集的应用程序。 可以看到,当这个程序在处理器为30个以上的系统上运行,系统的吞吐量会下降到80%以下,即本来在单CPU系统上只花1%时间做搜集的程序会在30个以上CPU的系统上,花上20%的时间做收集。 为什么会这样呢?因为单CPU收集算法在多CPU系统上面做收集的时候,GC算法会暂停运行在任何CPU上面的程序,而自己却只能利用一个CPU做GC,所以造成了这种情况。
代Generation
自从JVM1.2以后,Sun采用了一种分代收集的策略,即将堆分成3个不同的区域,按照对象存活的时间的不同,将对象保存在不同的堆上。在不同的代上面,应用不同的收集算法,来达到最优化收集。
这3个区域叫年轻代,年老代和持久代。 除Class和Method等元数据在持久代上面分配以外,所有的对象都在年轻代上面分配,当达到一定的条件,如经过在年轻代上面N次收集后的对象,就会保存到年老代之中。
在年轻代上面进行的收集叫Minor collections,在年轻代和年老代上面同时收集叫Major collections。
Tips: 尽量不要调用System.gc(),因为这会触发Major collections。Major collections的收集效率不高,因为它要遍历几乎所有的对象。 没有办法利用API直接触发Minor collections,但是仍然有其他的调优手段。当使用完集合对象后,把引用设置为null,这样避免gc在收集过程中,无谓地遍历那些即将就要释放的对象。
实验表明,年轻代上面的对象98%的对象都会在短时间内死亡,故Minor collections可以利用拷贝收集器,只遍历那些2%存活的对象,而不用管那些死亡的对象,来提高收集效率。 为了让Minor collections能充分利用年轻代上面对象大量死亡的这个特点,就需要调整以下几个参数: 1. 收集的频率 过于频繁的收集,会导致代中对象死亡率不够高,从而需要遍历这个代中大部分的对象,使得高死亡率这个条件利用的不充分。
2. 年轻代(堆)的大小 如果堆太小,一旦堆被占满,Minor collections就不得不频繁的启动,导致情况1的发生,从而降低收集效率。
下图反应了绝大多数对象在早起死亡的这一个事实: 
从图中可以看到,随着时间的推进,大部分分配的字节都被回收了,少部分留了下来。被回收的这些字节,就是所谓的die young,留下的则是live longer。
Bytes allocated 已经分配的总字节数 Bytes Surviving 存活的字节数 Minor Collections 对年轻代进行收集 Major Collections 对所有代进行收集 下图描述了JVM中对堆的划分: 
Young 年轻代 Tenured 年老代 Perm 持久代
Virutal是指保留,而未分配的内存。如Perm中,加上Virtual则是Perm区域最大的大小,而刚开始并不会完全分配这个堆,只会按照最小的大小分配。
Young部分,被分为了三个部分,一个Eden,和两个大小相同的Survivor。 所有的新建对象都会在Eden中分配,当Eden占满后,即剩余的大小不足以分配新的对象时,就会触发Minor collections。对Young收集时,会将对象拷贝到其中一个Survivor,另外一个Survivor保留不用。当下次收集时,则将上次Survivor中和Eden中的活动对象,拷贝到未用的Survivor中。如此反复。当某些对象经历足够长的次数或者时间后,就会被拷贝入年老代。 如果对Young的minor collections收集到的活动对象,survivor无法完全容纳,则会将某些对象拷贝到年老代,如果年老代也不能容纳新拷贝入的对象,则触发Major collections。如果Major collections后,如果还不足以容纳,就会将Virutal中预留的空间用来扩展已有的堆。当保留Virutal分配完毕后,仍然不足时,就会抛出OutOfMemory的错误。
Tips: 不要把年老代设置的过小,一般最好能比eden+survivor更大一些,这样可以避免触发Major collections。在这个前提下,年轻代越大越好。由于Young的eden区域是拷贝收集,容易产生碎片,所以此区域越大,越不容易导致因为碎片导致的内存不足而引发的minor collections。至于survivor,则要根据情况调整。过大的survior会造成浪费,过小的survior会导致,对象被直接拷贝到年老代。 JVM1.2中,未使用上面一大二小的结构,而是将Young分成两个相同大小的区域,来回进行拷贝。 调整GC的手段 
如何看懂上面那张图: 行,分别对应了三个代 列,分别对应了实时性要求比较高的默认的和可调节的选项, 以及对吞吐量比较高的默认的和可调节的选项,还有就是调整这3个堆的选项。 
-Xmx 调整JVM启动时,保留Total Size的大小 -Xms 调整JVM启动初始化时,Total Size的大小
每一次收集后,都会根据以下两个参数调整Total Size的大小
-XX:MinHeapFreeRatio(默认,40) Total Size中可用空间小于这个比率,就会扩展堆的大小,保持这个值 -XX:MaxHeapFreeRatio(默认,70) Total Size中可用空间大于这个比例,就缩小堆的大小,保持这个值 Tips: 如果你的程序是服务器,那么通过将-Xmx和-Xms设置成相近,或者相同,可以阻止这种堆大小的频繁调整,造成的不必要的收集和堆增长过程。 -XX:NewRatio=3 年老代和年轻代的内存分配比率,3表示,在Total Size中,年轻代占1份,年老代3份 -XX:NewSize 年轻代初始化时的大小 -XX:MaxNewSize 如果不指定,那么年轻代可以增长不受限制,但受NewRatio的限制 -XX:SurvivorRato=6 年轻代中,eden与survivor的比率,这里eden占6份,2个survivor占2份 -XX:MaxTenuringThreshold=0 阀值,超过这个阀值的对象将被拷贝到年老代 如何进行GC调优? 1.性能的几个重要度量参数 Troughput 吞吐量,除掉GC所用的时间后,真正执行程序所在总时间的百分比 Pauses 暂停时间,即由于正在做GC,而没有响应的那些时间 Footprint 内存需求,通常用page和cache line的数量来衡量
2.查看当前的GC收集 java命令运行时,输入 -verbose:gc 参数
[GC 4802K->4383K(5312K), 0.0078566 secs] //执行了Minor collections [Full GC 4383K->4383K(5312K), 0.0385521 secs] //执行了Major collections [GC 5248K->4814K(8216K), 0.0121798 secs] //空间仍然不够,扩展了年轻代和年老代的空间 以第一行为例 GC 表示执行了Minor collections 4802K->4383K 表示GC执行前和执行后,堆中活动对象的大小 (5312K) 表示总的堆的大小(不算持久代,而且只算2个Survivor中的1个,即用户可用堆的大小) 0.0078566 secs 表示GC所用的时间。主要,也是首先看这个,然后再看GC/Full GC是否过于频繁。 这个信息反应了什么?年轻代太小,因为执行了Minor collections之后,活动的对象并没有显著减少,4802K->4383K, 说明初始堆分配的不够大。这个GC显然是因为eden内部碎片导致的。 用-XX:+PrintGCDetails 参数,打印更详细的信息
[GC [DefNew: 64575K->959K(64576K), 0.0457646 secs] 196016K->133633K(261184K), 0.0459067 secs]] // Minor collections从年轻代64575K中收集到了959K的活动对象,花费了4微秒多一点 DefNew: 64575K->959K(64576K), 0.0457646 secs //整个堆整理后,从196016K中,收集到了133633K的活动对象 196016K->133633K(261184K) 还可以用-XX:+PrintGCTimeStamps 查看带起始和终止时间戳的信息 还有-XX:-PrintTenuringDistribution 打印出对象在放入年老代之前在年轻代做了多少次复制,如果复制次数过少,说明年轻代过小。 2类收集器 The Throughput Collector 以提高吞吐量,降低GC时间比的收集策略 The Concurrent Low Pause Collector 以提高暂停时间,以实时性为目的的收集策略 具体请看资料[1]中的内容。 其他 关于,如何遍历年轻代中的活动对象的技术,请看资料[3]。 参考资料:
1.Tuning Garbage Collection with the 5.0 Java[tm] Virtual Machine http://java./docs/hotspot/gc5.0/gc_tuning_5.html
2.Garbage Collector Ergonomics http://java./j2se/1.5.0/docs/guide/vm/gc-ergonomics.html
3.JVM1.4.1中的垃圾收集 http://www.ibm.com/developerworks/cn/java/j-jtp11253/
|