标记-清除算法
- 标记阶段: 标记所有还被引用的对象(从GC Roots出发)
- 清除阶段: 遍历清除未被标记的对象
缺点
: 产生了内存碎片,需要两次遍历,效率低,进行GC时需要stop the world,用户体验感差
标记-复制算法
将内存区域分为两块A、B,每次只使用一块,然后将被引用的对象复制到另外一块未使用的内存区域,然后清除正在使用的内存区域,以此完成回收,然后交换两块区域的角色。
在新生代中,每次将eden区和survivor1区中还存在的对象一次性复制到survivor2区,然后清除eden区和survivor1区使用的空间,最后交换survivor1区和survivor2区。
优点
:不会尝生内存碎片,没有标记清除两个阶段,实现比较简单
缺点
:需要两倍空间,存活对象多的情况下,效率很低
应用场景
: 新生代,因为新生代的对象都是朝生夕死的,每次大概有70%-99%的内存空间能被回收掉。
标记-整理算法
- 标记阶段:跟标记-清除算法一样,先标记出被引用的对象
- 整理阶段:让所有的被标记对象往内存区域的一端去移动,然后清理掉在这个区域以外的对象
优点
:不会产生内存碎片,不需要内存减半
缺点
:比标记-复制算法慢,移动对象的时候,得调整对象的引用地址,需要STW(stop the world)
标记清除 | 标记复制 | 标记整理 | |
---|---|---|---|
速度 | 中等 | 最快 | 最慢 |
空间开销 | 少(堆积碎片) | 需要两倍的空间(空间减半) | 少(不会堆积碎片) |
移动对象 | 否 | 是 | 是 |
所以总的来说,三种垃圾回收算法都有各自的优缺点,标记复制算法效率最高,但是却需要空间减半。
分代收集理论:
当前商业虚拟机的垃圾收集器,大多数都遵循了“分代收集”(GenerationalCollection)的理论进行设计,分代收集名为理论,实质是一套符合大多数程序运行实际情况的经验法则,它建立在两个分代假说之上:
弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的。
强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡。
这两个分代假说共同奠定了多款常用的垃圾收集器的一致的设计原则:收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。显而易见,如果一个区域中大多数对象都是朝生夕灭,难以熬过垃圾收集过程的话,那么把它们集中放在一起,每次回收时只关注如何保留少量存活而不是去标记那些大量将要被回收的对象,就能以较低代价回收到大量的空间;如果剩下的都是难以消亡的对象,那把它们集中放在一块,虚拟机便可以使用较低的频率来回收这个区域,这就同时兼顾了垃圾收集的时间开销和内存的空间有效利用。
分代回收算法
目前,所有的GC都是使用分代回收算法进行GC的。
一般来说,Java堆分为新生代和老年代。
堆内存大小 = 新生代+老年代 from区和to区也叫 survivor1区和survivor2区
在新生代中,最合适的算法是复制算法,因为很多对象都是朝生夕灭,然后survivor占的空间也不多,此时使用标记复制算法肯定是最佳的;
在老年代,因为对象的存活率比较高,所以我们可以选择标记清除或者标记整理算法进行垃圾回收。
所以,Hotspot进行了分区,也是为了提高垃圾回收的一个效率!
新生代的垃圾回收过程
- 当eden区满了的时候,此时会触发一次MinorGC,把被标记还活着的对象复制到from区中,然后将eden清空;然后当eden区再次满了的时候,此时会将eden和from区中被标记还活着的对象复制到to区,然后将eden和from清空。(此时我们将from和to区交换一下)
- 然后接下来的每一次垃圾回收,都可以看做是一个eden+from复制到to区的一个过程。当GC年龄超过15岁还存活的话,则会被送进老年区。