1.标记-清除算法–Mark-Sweep
算法分为两个阶段
- 标记—标记处所需要回收的对象
- 清除—回收所有需要回收的对象
缺点
- 效率控制—两个阶段效率不高
- 空间问题—标记清理之后会产生大量不连续的内存碎片,空间碎片过多可能会导致后续使用中无法找到足够连续的内存而提前触发一次垃圾搜集动作
综合所述
- 标记清理算法效率不高,需要扫描所有的对象. 堆越大,GC越慢
- 存在内存碎片问题,GC次数越多,碎片越严重
2.标记-整理算法–Mark-Compact
其标记过程与标记-清除算法一致
但后续步骤不是进行直接清理,而是令所有存活的对象往一端移动,然后直接清理掉这边边界以外的内存
其特点如下:
- 没有内存碎片
- 比标记整理算法耗费更多的时间进行Compact
3.复制算法–Copying
该算法将内存划分为两块
- 每次只使用其中的一块
- 当半区内存用完了,仅将还存活的对象复制到另外一块上面
- 然后就把原来整块内存空间一次性清理掉
这样使得每次内存回收都是对整个半区的回收
- 内存分配时也就不用考虑内存碎片的等复杂情况
- 只要移动堆顶指针,按顺序分配内存就可以了,
- 实现简单,运行高效
缺点
-
内存缩小为原来的一半,代价高昂
-
在对象存活率高的时候,效率会有所下降
-
现在的商业虚拟机都是用了这一种收集算法来回收
新生代
其原理是 -
将内存分为1块较大的Eden空间和2块较少的Survivor空间
-
每次使用Eden和其中一块Survivor
-
当回收时将Eden和Survivor还存活的对象一次性拷贝到另外一块Survivor空间上
-
然后清理掉Eden和用过的Survivor
Oracle Hotspot虚拟机默认的Eden和Survivor的大小比例是8:1.也就是每次只有10%的内存是"浪费"的
如果不想浪费50%的空间,就需要有额外的空间进行分配担保用于应付半区内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法
该算法的特点如下
- 只需要扫描存活的对象,效率更高
- 不会产生碎片
- 需要浪费额外的内存作为复制区
- 该算法非常适合生命周期比较短的对象—因为每次GC总能回收大部分的对象,复制的开销比较小
- 根据IBM的专门研究,98%的Java对象只会存活1个GC周期,对这些对象和适合用复制算法,且不用1:1的划分工作区和复制区的空间
4.分代算法–Generational
综合前面几种GC算法的优缺点,针对不同生命周期的对象采用不同的GC算法
当前商业虚拟机的垃圾回收都是采用"分代收集"Generational Collecting
算法,根据对象不同的存活周期将内存划分为几块
一般把Java堆分作
新生代
老年代
这样就可以根据各个年代的特点采用最适当的收集算法
譬如:
- 新生代每次GC都有大批对象死去,只有少量存活,那就选用复制算法—只需要付出少量的存活对象的复制成本就可以完成收集
Hotspot JVM 6*划分为三个代
- 年轻代 Young Generation
其分为- Eden Space
- From Space
- To Space
- 新生成的对象都放在新生代
- 年轻代用复制算法进行GC—理论上年轻代对象的生命周期非常短,所以适合复制算法
- 年轻代分为三个区,一个Eden和两个Survivor–可通过参数设置Survivor个数
其执行步骤如下:
- 对象在Eden区中生成
- 当Eden区满时,还存活的对象被复制到一个Survivor区
- 当这个Survivor区满时,次去的存活对象被负值到另外一个Survivor区
- 当第二个Survivor去也满了的时候,从第一个Survivor去复制过来的并且此时还存活的对象,将被复制到老年代.
- 2个Survivor是完全对称的,轮流替换
Eden和2个Survivor的默认比例是8:1:1,也就是10%的空间会被浪费.可以根据GC log的信息调整大小的比例
-
老年代 Old Generation
- Tenured Space
老年代存放了经过一次或多次GC还存活的对象
一般采用标记清除或标记整理算法进行GC - 老年代有多种垃圾收集器可以选择
- 每种垃圾收集器可以看做一个GC算法的具体实现
- 可以根据应用的需求选用合适的垃圾收集器—以追求吞吐量和最短响应时间
- Tenured Space
-
永久代 Permanent Generation- Permanent Space
永久代并不属于堆,但是GC也会涉及到这个区域
其存放了每个Class的结果信息
包括 - 常量池
- 字段描述
- 方法描述
- Permanent Space
与垃圾收集要收集的Java对象关系不大