jvm垃圾回收

在这节里不打算大量讨论算法实现,只是简单的介绍一下基本思想以及发展过程。最基础的搜集算法是 标记-清除算法 Mark-Sweep ),如它的名字一样,算法分层 标记 清除 两个阶段,首先标记出所有需要回收的对象,然后回收所有需要回收的对象,整个过程其实前一节讲对象标记判定的时候已经基本介绍完了。说它是最基础的收集算法原因是后续的收集算法都是基于这种思路并优化其缺点得到的。它的主要缺点有两个,一是效率问题,标记和清理两个过程效率都不高,二是空间问题,标记清理之后会产生大量不连续的内存碎片,空间碎片太多可能会导致后续使用中无法找到足够的连续内存而提前触发另一次的垃圾搜集动作。

  为了解决效率问题,一种称为 复制 Copying )的搜集算法出现,它将可用内存划分为两块,每次只使用其中的一块,当半区内存用完了,仅将还存活的对象复制到另外一块上面,然后就把原来整块内存空间一次过清理掉。这样使得每次内存回收都是对整个半区的回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存就可以了,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半,未免太高了一点。  

  现在的商业虚拟机中都是用了这一种收集算法来回收新生代, IBM 有专门研究表明新生代中的对象 98% 是朝生夕死的,所以并不需要按照 1 1 的比例来划分内存空间,而是将内存分为一块较大的 eden 空间和 2 块较少的 survivor 空间,每次使用 eden 和其中一块 survivor ,当回收时将 eden survivor 还存活的对象一次过拷贝到另外一块 survivor 空间上,然后清理掉 eden 和用过的 survivor Sun Hotspot 虚拟机默认 eden survivor 的大小比例是 8:1 ,也就是每次只有 10% 的内存是 浪费 的。当然, 98% 的对象可回收只是一般场景下的数据,我们没有办法保证每次回收都只有 10% 以内的对象存活,当 survivor 空间不够用时,需要依赖其他内存(譬如老年代)进行分配担保( Handle Promotion )。  

  复制收集算法在对象存活率高的时候,效率有所下降。更关键的是,如果不想浪费 50% 的空间,就需要有额外的空间进行分配担保用于应付半区内存中所有对象都 100% 存活的极端情况,所以在老年代一般不能直接选用这种算法。因此人们提出另外一种 标记-整理 Mark-Compact )算法,标记过程仍然一样,但后续步骤不是进行直接清理,而是令所有存活的对象一端移动,然后直接清理掉这端边界以外的内存。  

  当前商业虚拟机的垃圾收集都是采用 分代收集 GenerationalCollecting )算法,这种算法并没有什么新的思想出现,只是根据对象不同的存活周期将内存划分为几块。一般是把 Java 堆分作新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法,譬如新生代每次 GC 都有大批对象死去,只有少量存活,那就选用复制算法只需要付出少量存活对象的复制成本就可以完成收集。

你可能感兴趣的:(jvm垃圾回收)