龙盟编程博客 | 无障碍搜索 | 云盘搜索神器
快速搜索
主页 > 软件开发 > JAVA开发 >

Java虚拟机JVM性能优化(三):垃圾收集详解(4)

时间:2014-09-20 11:09来源:网络整理 作者:网络 点击:
分享到:
内存溢出错误会挂起进程,日志显示垃圾收集器正在超负荷工作,这些都显示垃圾收集正试图释放内存,也表明堆中碎片很多。一些程序员会试图通过再次

内存溢出错误会挂起进程,日志显示垃圾收集器正在超负荷工作,这些都显示垃圾收集正试图释放内存,也表明堆中碎片很多。一些程序员会试图通过再次优化垃圾收集器来解决碎片化问题。但我认为应该寻找更有新意的办法解决这个问题。接下来的部分将重点讨论解决碎片化的两个办法:分代垃圾收集和压缩。

分代垃圾收集

你可能听过这样的理论:在生产环境中绝大多数对象的存活时间都很短。分代垃圾收集正是由这一理论衍生出的一种垃圾收集策略。在分代垃圾收集中,我们将堆分为不同的空间(或者叫做代),每个空间中保存着不同年龄的对象,所谓对象的年龄就是对象存活的垃圾收集周期数(也就是该对象多少个垃圾收集周期后仍然被引用)。

当新生代没有剩余空间可分配时,新生代的活动对象会被移动到老年代中(通常只有两个代。译者注:只有满足一定年龄的对象才会被移动到老年代),分代垃圾收集常常使用单向的复制收集器,一些更现代的JVM新生代中使用的是并行收集器,当然也可以为新生代和老年代分别实现不同的垃圾收集算法。如果你使用并行收集器或复制收集器,那么你的新生代收集器就是一个stop-the-world的收集器(参见之前的解释)。

老年代分配给那些从新生代移出的对象,这些对象要么是被引用很长一段时间,要么是被一些新生代中对象集合所引用。偶尔也有大对象直接被分配到了老年代,因为移动大对象的成本相对较高。

分代垃圾收集技术

在分代垃圾收集中,老年代运行垃圾收集的频率较低,而在新生代运行垃圾收集的频率较高,而我们也希望在新生代中垃圾收集周期更短。在极少的情况下,新生代的垃圾收集可能会比老年代的垃圾收集更频繁。如果你将新生代设置的太大时并且应用程序中的多数对象都存活较长时间,这种情况就可能会发生。在这种情况下,如果老年代设置的太小以至于无法容纳所有的长时间存活的对象,老年代的垃圾收集也会挣扎于释放空间给那些被移动进来的对象。不过通常来说分代垃圾收集可以使应用程序获得更好的性能。

划分出新生代的另一个好处是某种程度上解决了碎片化问题,或者说将最坏的情况推迟了。那些存活时间短的小对象本来可能产生碎片化问题,但都在新生代的垃圾收集中被清理了。由于存活时间长的对象被移到老年代时被更紧凑的分配空间,老年代也更加紧凑了。随着时间推移(如果你的应用运行时间足够长),老年代也会产生碎片化,这时需要运行一次或是几次完全垃圾收集,同时JVM也有可能抛出内存溢出错误。但是划分出新生代推迟了出现最坏情况的时间,这对于很多应用程序来说已经足够了。对于多数应用程序而言,它的确降低了stop-the-world垃圾收集的频率和内存溢出错误的机会。

优化分代垃圾收集

正如之前提到的,使用分代垃圾收集带来了重复的调优工作,例如调整新生代大小、提升率等。我无法针对具体应用运行时来强调怎样做取舍:选择固定的大小固然可以优化应用程序,但同时也减少了垃圾收集器应对动态变化的能力,而变化是不可避免的。

对于新生代首要原则就是在确保stop-the-world垃圾收集期间延迟时间前提下尽可能的加大,同时也要为那些长期存活的对象在堆中保留足够大的空间。下面是在调整分代垃圾收集器时要考虑的一些额外因素:

1.新生代中多数都是stop-the-world垃圾收集器,新生代设置的越大,相应的暂停时间就越长。因此对于那些受垃圾收集暂停时间影响大的应用程序来说,要仔细考虑将新生代设置为多大合适。

2.可以在不同的代上使用不同的垃圾收集算法。例如在新生代中使用并行垃圾收集,在老年代中使用并发垃圾收集。

3.当发现频繁的提升(译者注:从新生代移动到老年代)失败时说明老年代中碎片太多了,也就是说老年代中没有足够的空间来存放从新生代移出的对象。这时你可以调整一下提升率(即调整提升的年龄),或者确保老年代中的垃圾收集算法会进行压缩(将在下一段讨论)并调整压缩以适应应用程序的负载。也可以增加堆大小和各个代大小,但是这样更会进一步延长老年代上的暂停时间。要知道碎片化是无法避免的。

精彩图集

赞助商链接