垃圾收集算法的实现涉及大量的程序细节,而且各个平台的虚拟机操作内存的方法又各不相同,所以只介绍几种算法的思想及其发展过程。
JVM常见的GC算法有以下四种:
标记-清除算法(Mark-Sweep)
标记-整理算法(Mark-Compact)
复制算法(Copying)
分代算法(Generational)
一、标记-清除算法
算法分为“标记”和“清除”两个阶段,首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的待回收对象。这种算法主要有两个缺点:第一个是效率问题,标记和清理两个过程效率都不高,需要扫描所有的对象。堆越大,GC越慢;然后就是空间问题,标记清理之后会产生大量不连续的内存碎片,空间碎片太多可能会导致后续使用中无法找到足够的连续内存而提前触发另一次的垃圾搜集动作,并且GC次数越多,碎片问题越严重。
下图展示对象被回收前状态:
被标记后:
被标记对象被回收后:
二、复制(Copying)算法
这种算法的思想是:将可用内存划分为两块,每次只使用其中的一块,当半区内存用完了,仅将还存活的对象复制到另外一块上面,然后就把原来整块内存空间一次性清理掉。如下图所示:
这种方式也有其优缺点,这样使得每次内存回收都是对整个半区的回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存就可以了,并且每次只需要扫描存活的对象,实现简单,运行高效,不会产生碎片。只是这种算法的代价是将内存缩小为原来的一半,代价高昂。
针对上面问题,现在的商业虚拟机都采用这种收集算法来回收新生代,因为新生代中的对象98%是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间。而是将内存分为一块较大的eden空间和2块较少的survivor空间,每次使用eden和其中一块survivor,当回收时将eden和survivor还存活的对象一次性拷贝到另外一块survivor空间上,然后清理掉eden和用过的survivor。Oracle HotSpot 虚拟机默认eden和survivor的大小比例是 8:1,也就是每次只有10%的内存是“浪费”的。
三、标记-整理(Mark-Compact)算法
复制收集算法在对象存活率高的时候,效率有所下降。并且如果不想浪费50%的空间,就需要有额外的空间进行分配担保用于应付半区内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。
根据老年代的特点,有人提出另外一种“标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是进行直接清理,而是令所有存活的对象一端移动,然后直接清理掉这端边界以外的内存。
这种算法也不会产生内存碎片,但是比Mark-Sweep耗费更多的时间进行compact。
四、分代收集(Generational Collecting)算法
当前商业虚拟机的垃圾收集都是采用“分代收集”算法,根据对象不同的存活周期内存划分为几块。即综合前面几种GC算法的优缺点,针对不同生命周期的对象采用不同的GC算法。
一般是把Java堆分作新生代和老年代,这样就可以根据各个年代的特点采用最适合的收集算法,譬如新生代每次GC都有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本可以完成收集。
下面以 HotSpot JVM 6举例(注意是第6版,因为Permanent Generation)。HotSpot JVM 6中共划分为三个代:年轻代(Young Generation)、老年代(Old Generation)和永久代(Permanent Generation)。
年轻代(Young Generation)
新生成的对象都放在新生代。年轻代用复制算法进行GC(理论上,年轻代对象的生命周期非常短,所以适合复制算法)
年轻代分为三个区。一个Eden区,两个Survivor区(可以通过参数设置Survivor个数)。对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到一个Survivor区,当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当第二个Survivor区也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制到老年代。2个Survivor是完全对称的,轮流替换。
Eden和2个Survivor的缺省比例是 8:1:1,也就是10%的空间会被浪费。可以根据GC log的信息调整大小比例。
老年代(Old Generation)
存放了经过一次或多次GC还存活着的对象
一般采用Mark-Sweep或者Mark-Compact算法进行GC
有多种垃圾收集器可以选择。每种垃圾收集器可以看作一个GC算法的具体实现。可以根据具体应用的需求选用合适的垃圾收集器(追求吞吐量?追求最短的响应时间?)
永久代
并不属于堆(Heap)。但是GC也会涉及到这个区域
存放了每个Class的结构信息,包括常量池、字段描述、方法描述。与垃圾收集要收集的Java对象关系不大。