文章目录
- 1. 标记-清除算法(Mark and Sweep)
- 2. 复制算法(Copying)
- 3. 标记-整理算法(Mark and Compact)
- 4. 分代算法(Generational)
- 4.1 执行流程
1. 标记-清除算法(Mark and Sweep)
标记清除算法,是将垃圾回收分为2个阶段,分别是标记和清除。
- 标记阶段:从根对象出发,通过可达性分析算法标记所有可达对象。
- 清除阶段:遍历整个堆内存,对未被标记的对象进行回收。
优点:解决了解决了引用计数算法中的循环引用的问题,没有从root节点引用的对象都会被回收,能够处理不连续的内存碎片。
缺点:1. 效率较低,标记和清除两个动作都需要遍历所有的对象,并且在GC时,需要停止应用程序,对于交互性要求比较高的应用而言这个体验是非常差的。2. 通过标记清除算法清理出来的内存,碎片化较为严重,因为被回收的对象可能存在于内存的各个角落,所以清理出来的内存是不连贯的。
2. 复制算法(Copying)
复制算法的核心是将堆内存分为两个区域:一个存放活动对象(From Space),一个用于回收和分配新对象(To Space),每次只用其中的一个。当From Space内存占用达到一定比例时,将存活的对象复制到To Space中,并清空From Space。完成垃圾的回收。
如果内存中的垃圾对象较多,需要复制的对象就较少,这种情况下适合使用该方式并且效率比较高,反之,则不适合。
优点:在垃圾对象多的情况下,效率较高,清理后没有内存碎片,分配内存简单高效。
缺点:需要额外的空间来存放活动对象,且无法处理大对象和长期存活对象,内存使用率较低。
3. 标记-整理算法(Mark and Compact)
标记整理算法是在标记清除算法的基础之上,做了优化改进。和标记清除算法一样,也是从根节点开始,对对象的引用进行标记,在清理阶段,并不是简单的直接清理可回收对象,而是将存活对象都向内存另一端移动,然后清理边界以外的垃圾,从而解决了碎片化的问题。
优点:解决了标记-清除算法产生的内存碎片问题,同时,标记压缩算法多了一步,对象移动内存位置的步骤,其效率也有有一定的影响。
缺点:移动对象需要更多的开销,可能会导致停顿时间较长。
与复制算法对比:复制算法标记完就复制,但标记整理算法得等把所有存活对象都标记完毕,再进行整理。
4. 分代算法(Generational)
将堆内存划分为不同的代(Generation),一般分为新生代(Young Generation)、老年代(Old Generation)和永久代/元空间(Permanent Generation/Metaspace)。在java8时,堆被分为了两份:新生代和老年代【1:2】,在java7时,还存在一个永久代。
大部分对象首先分配到新生代,只有经过多次回收仍然存活的对象才会晋升到老年代。对于新生代,内部又被分为了三个区域。Eden区,frm区,to区【8:1:1】
- 当对新生代产生GC:MinorGC【young GC】
- 当对老年代代产生GC:Major GC
- 当对新生代和老年代产生FullGC: 新生代 + 老年代完整垃圾回收,暂停时间长,应尽力避免,针对不同代使用不同的回收算法,如新生代常使用复制算法,而老年代则使用标记-清除或标记-整理算法。
优点:根据对象的生命周期,针对性地选择回收算法,提高了垃圾收集的效率。
缺点:需要维护不同代之间的引用关系和内存分配比例,增加了管理成本。
4.1 执行流程
- 新创建的对象,都会先分配到Eden区
- 当Eden内存不足,标记Eden与 from(现阶段没有)的存活对象将存活对象采用复制算法复制到 to 中,复制完毕后,Eden和 from 内存都得到释放
- 经过一段时间后Eden的内存又出现不足,标记to区存活的对象,将存活的对象复制到from区
- 当幸存区对象熬过几次回收(最多15次),晋升到老年代(幸存区内存不足或大对象会导致提前晋升)