垃圾回收阶段算法
当成功区分出内存中存活对象和死亡对象后,GC 接下来的任务就是执行垃 圾回收,释放掉无用对象所占用的内存空间,以便有足够的可用内存空间为新对 象分配内存。目前在 JVM 中比较常见的三种垃圾收集算法是:
标记-清除算法(Mark-Sweep)
复制算法(copying)
标记-压缩算法(Mark-Compact)
1.标记-清除算法:
当堆中的有效内存空间(available memory)被耗尽的时候,就会停止整个程序(也被称为 stop the world),然后进行两项工作,第一项则是标记, 第二项则是清除.
标记:Collector 从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的 Header中记录为可达对象。(注意:标记的是被引用的对象,也就是可达对象,并非标记的是即将被清除的垃圾对象)。
清除:Collector 对堆内存从头到尾进行线性的遍历,如果发现某个对象在 其 Header 中没有标记为可达对象,则将其回收。
注意:何为清除?
这里所谓的清除并不是真的置空,而是把需要清除的对象地址保存在空闲的地址列表里。下次有新对象需要加载时,判断垃圾的位置空间是否够,如果够,就存放(也就是覆盖原有的地址).
(分为两个阶段,一个是标记阶段,一个是清除阶段.
标记:标记出从根可达的对象,标记的是被引用的对象.
清除:此清除并非直接将垃圾对象清除掉,而是将垃圾对象的地址维护到一个空闲列表中,之后如果有新的对象产生,判断空闲列表中对象空间能否存放的下新的对象,如果能放下,那么就覆盖垃圾对象.
)
标记-清除算法的优点:
非常基础和常见的垃圾收集算法容易理解
标记-清除算法的缺点:
标记清除算法的效率不算高 在进行 GC 的时候,需要停止整个应用程序,用户体验较差 .
这种方式清理出来的空闲内存是不连续的,产生内碎片,需要维护一个空闲列表。(空闲列表-记录垃圾对象地址).
(概述
优点:简单,容易理解.
缺点:效率低,会产生STW(在回收时,会停止整个应用程序),会产生内存碎片.
)
2 复制算法:
为了解决标记-清除算法在垃圾收集效率方面的缺陷,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收.
优点
没有标记和清除过程,实现简单,运行高效,复制过去以后保证空间的连续性,不会出现“碎片”问题。
缺点
此算法的缺点也是很明显的,就是需要两倍的内存空间。 对于 G1 这种分拆成为大量 region 的 GC,复制而不是移动,意味着 GC 需要维护 region 之间对象引用关系,不管是内存占用或者时间开销也不小.
(概述:
优点: 运行高效,减少内存碎片.
缺点:要用到2倍的内存空间,对于G1垃圾回收器,将每个区域拆分成更多的小区域,需要维护各区域之间的关系.
)
复制算法的应用场景
如果系统中的垃圾对象很多,复制算法需要复制的存活对象数量并不会太大,效率较高
老年代大量的对象存活,那么复制的对象将会有很多,效率会很低 在新生代,对常规应用的垃圾回收,一次通常可以回收 70% - 99% 的内存空间。 回收性价比很高。所以现在的商业虚拟机都是用这种收集算法回收新生代.
3 标记-压缩算法
背景
复制算法的高效性是建立在存活对象少、垃圾对象多的前提下的。这种情况在新生代经常发生,但是在老年代,更常见的情况是大部分对象都是存活对象。 如果依然使用复制算法,由于存活对象较多,复制的成本也将很高。因此,基于老年代垃圾回收的特性,需要使用其他的算法。
标记-清除算法的确可以应用在老年代中,但是该算法不仅执行效率低下, 而且在执行完内存回收后还会产生内存碎片,所以 JVM 的设计者需要在此基础 之上进行改进。
(复制算法需要移动对象位置,移动的数量多的情况下,效率低,对于年轻代来讲还是不错的,
对于老年代,大量的对象是存活的如果移动就比较麻烦.效率低)
第二阶段将所有的存活对象压缩到内存的一端,按顺序排放。之后,清理边界外 所有的空间。
标记-压缩算法的最终效果等同于标记-清除算法执行完成后,再进行一次内存碎片整理,因此,也可以把它称为标记-清除-压缩(Mark-Sweep-Compact)算法。
二者的本质差异在于标记-清除算法是一种非移动式的回收算法(空闲列表记录位置),标记-压缩是移动式的。是否移动回收后的存活对象是一项优缺点并存的风险决策。
可以看到,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉。如此一来,当我们需要给新对象分配内存时,JVM 只需要持有一个内存的起始地址即可,这比维护一个空闲列表显然少了许多开销。
(概述:
标记清除不移动对象,不会吧对象清除掉(维护在一个空闲列表中)
标记-压缩算法是要移动对象的,要清除掉垃圾对象.)
优点
消除了标记-清除算法当中,内存区域分散的缺点,我们需要给新对象分配内存时,JVM 只需要持有一个内存的起始地址即可。 消除了复制算法当中,内存减半的高额代价。
缺点
从效率上来说,标记-整理算法要低于复制算法。 移动对象的同时,如果对象被其他对象引用,则还需要调整引用的地址移动过程中,需要全程暂停用户应用程序。即:STW.
4 垃圾回收算法小结
效率上来说,复制算法是当之无愧的老大,但是却浪费了太多内存。
而为了尽量兼顾上面提到的三个指标,标记-整理算法相对来说更平滑一些,但 是效率上不尽如人意,它比复制算法多了一个标记的阶段,比标记-清除多了一 个整理内存的阶段。
5 分代收集
前面所有这些算法中,并没有一种算法可以完全替代其他算法,它们都具有自己 独特的优势和特点。分代收集应运而生。
分代收集,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不 同生命周期的对象可以采取不同的收集方式,以便提高回收效率。一般是把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点使用不同的回收算法, 以提高垃圾回收的效率。
(分代/分区收集:
由于对象的生命周期长短不同,将不同的对象存储在不同的区域.针对不同的区域进行分区收集,提高收集效率.)
年轻代(Young Gen)
年轻代特点:区域相对老年代较小,对象生命周期短、存活率低,回收频繁。
这种情况复制算法的回收整理,速度是最快的。复制算法的效率只和当前存活对 象大小有关,因此很适用于年轻代的回收。而复制算法内存利用率不高的问题, 通过 hotspot 中的两个 survivor 的设计得到缓解。
老年代(Tenured Gen)
老年代特点:区域较大,对象生命周期长、存活率高,回收不及年轻代频繁。
这种情况存在大量存活率高的对象,复制算法明显变得不合适。一般是由标记- 清除或者是标记-清除与标记-整理的混合实现。 1.Mark 阶段的开销与存活对象的数量成正比。 2.Sweep 阶段的开销与所管理区域的大小成正相关。 3.Compact 阶段的开销与存活对象的数据成正比。
\分代的思想被现有的虚拟机广泛使用。几乎所有的垃圾回收器都区分新生代和老年代。