分类: Java
2014-06-01 14:05:46
今天来总结一下最近学习到的JVM垃圾收集算法和垃圾收集器。原来以为这部分知识,对于一个Java程序员的用处不大,可是如果你的运营Web项目,出现了性能问题,那么这部分知识可以帮助你从运维的角度来优化你的项目。
首先,先讲一下JVM的垃圾收集算法,因为这是学习垃圾收集器的前堤。各种垃圾收集器也都是基于这些算法来实现的。
JVM垃圾收集算法JVM垃圾收集算法有四种:标记-清除算法、复制算法、标记-整理算法、分代收集算法
标记-清除算法:该算法如同它的名字一样,分为两个阶段:标记、清除。首先标记出所有需要回收的对象,然后,统一清除这些被标记的对象。该算法的缺点是:1、效率不高;2、产生大量不连续的内存碎片,导致有大量内存剩余的情况下,由于,没有连续的空间来存放较大的对象,从而触发了另一次垃圾收集动作。
复制算法:由于标记-清除算法的效率不高,从而提出了复制算法。复制算法将可用的内存分成两样大小的两块,每次只使用其中一块内存。当这块内存用完之后,就把还存活的对象复制到另外一块上面,然后,把这块清空。复制算法克服了标记-清除算法的两个缺点,但是太浪费内存,相当于内存空间减小了一半。
随着时间的积累,现在使用的复制算法的虚拟机,不再是把内存分为1:1的两块。因为98%的对象是寿命很短的,创建之后,很快就被回收了,存活下来的只有2%,所以,用来存储存活对象的内存区,可以小一些。现在的商业虚拟机是把可用内存分为一个较大的Eden空间和两个较小的Survivor空间,每次使用Eden和其中的一块Survivor。当回收时,把Eden和Survivor中的存活对象一次复制到另一块Survivor内存区上,然后把Eden和刚才用过的Survivor空间清空。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,这样,每次新产生的对象可以使用90%的内存空间。
标记-整理算法从名字可以看出,该算法是对“标记-清除”算法的改进升级版。同样的该算法分为两个阶段:标记、整理。标记阶段同“标记-清除”算法。整理阶段,不是直接对标记对象进行清理,而是让所有存活的对象都移动到一端,然后,直接把边界以外的内存清空。这就解决了“标记-清除”算法会造成大量不连续内存碎片的问题。
分代收集算法分代收集算法是根据对象的存活周期的不同,将内存划分为几块。当前的商业虚拟机的垃圾收集都采用了该算法。一般把Java堆分成新生代(年轻代)和老年代(年老代)。这样就可以根据各年代中对象的存活周期来选择最合适的收集算法了。新生代,由于只有少量的对象能存活下来,所以选用“复制算法”,只需要付出少量存活对象的复制成本。老年代,由于对象的存活率高,没有额外的空间分担,就必须使用“标记-清除”或“标记-整理”算法。
JVM垃圾收集器
由于内存中的对象,是按存活周期存放在不同的内存块中的,所以,我们选择不同的算法来针对不同的内存块进行垃圾收集。从而,对于,不同的内存块,我们需要有不同的垃圾收集器。
新生代的垃圾收集器有:Serial收集器、ParNew收集器、Parallel Scavenge收集器
老年代的垃圾收集器有:Serial Old收集器、Parallel Old收集器、CMS收集器、G1收集器
下面我们来分别介绍一下这些垃圾收集器
Serial收集器/Serial Old收集器Serial收集器/Serial Old收集器,是单线程的,使用“复制”算法。当它工作时,必须暂停其它所有工作线程。特点:简单而高效。对于运行在Client模式下的虚拟机来说是一个很好的选择。
ParNew收集器ParNew收集器,是Serial收集器的多线程版。是运行在Server模式下的虚拟机中首选的新生代收集器。除了Serial收集器外,目前只有它能与CMS收集器配合工作。
Parallel Scavenge收集器/Parallel Old收集器Parallel Scavenge收集器,也是使用“复制”算法的、并行的多线程收集器。这些都和ParNew收集器一样。但它关注的是吞吐量(CPU用于运行用户代码的时间与CPU总消耗时间的比值),而其它收集器(Serial/Serial Old、ParNew、CMS)关注的是垃圾收集时用户线程的停顿时间。
Parallel Old收集器是Parallel Scavenge收集器的老年代版本。
CMS收集器CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,使用“标记-清除”算法。
CMS收集器分4个步骤进行垃圾收集工作:
1、初始标记 2、并发标记 3、重新标记 4、并发清除
其中“初始标记”、“重新标记”是需要暂停其它所有工作线程的。
G1收集器G1(Garbage First)收集器,基于“标记-整理”算法,可以非常精确地控制停顿。