JVM垃圾回收算法分析与演示
- 标记-清除算法(Mark-Sweep)
- 标记-整理算法(Mark-Compact)
- 复制算法(Copying)
- 分代算法(Generational)
★★★GC算法是内存回收的方法论,垃圾收集器就是算法的落地实现
-
标记-清除算法(Mark-Sweep)
- 算法分为是”标记“和”清除“两个阶段,首先标记出所有需要回收的对象,然后回收所有需要回收的对象。
- 缺点:
- 效率问题:标记和清理两个过程效率都不高;(需要扫描所有对象。堆越大,GC越慢)
- 空间问题:标记清理之后会产生大量不连续的内存碎片,空间碎片太多可能会导致后续使用中无法找到足够的连续内存而提前一次触发另一次的垃圾收集动作。(GC次数越多,碎片越严重)
-
绿色标记的,下一次垃圾回收不会被回收掉;红色的将会别回收掉!
-
复制算法(Copying)
- 将可用内存划分为两块,每次只使用其中的一块,当半区内存用完了,仅将还存活的对象复制到另外一块上面,然后就把原来整块内存空间一次性清理掉;
- 这样使得每次内存回收都是整个半区的回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存就可以了,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半,代价高昂;
- 现在的商业虚拟机中都是用了这一种收集算法来回收新生代;
- 将内存分为一块较大的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的划分工作区和复制区的空间。
-
标记-整理算法(Mark-Compact)
- 标记过程仍然一样,但后续步骤不是进行直接清理,而是令所有存活的对象一端移动,然后直接清理掉这端边界以外的内存;
- 没有内存碎片;
- 比Mark-Sweep耗费更多的时间进行compact。
-
分代收集算法(Generational Collecting)
- 当期商业虚拟机的垃圾收集都是采用”分代收集“(Generational Collecting)算法,根据对象不同的存活周期将内存划分为几块;
- 一般是把Java堆分作新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法,譬如:新生代每次GC都有大批对象死去,只有少量存活,那就选用复制算法只需要少量存活对象的复制成本就可以完成收集。
-
综合前面几种GC算法的优缺点,针对不同生命周期的对象采用不同的GC算法:
-
Hotspot JVM 6中划分为三个代:年轻代(Young Generation)、老年代(Old Generation)和永久代(Permanent Generation)