GC 三大算法

GC 算法基本上是基于根可达算法的 , 在 Java 中采用可达性分析算法来判断对象是否是垃圾,以 GCRoots 为根节点,从这些节点向下搜索,所遍历过的路径称为引用链 ,如果某一个对象到 GC Roots 没有任何引用链(即 GC Roots 到对象不可达)时,则证明此对象是不可用的。没有引用的对象是要回收的 ,而根可达性算法就是为了标记出哪些是存活的对象有引用链的就是存活的 , 没有引用链的对象就是垃圾对象

 

从周志明老师的 <深入理解 Java 虚拟机> 中的这张图可以看出 根可达分析的流程 , 以及它解决了引用计数法的循环引用问题

根可达分析算法怎么就解决循环引用问题了 ? 

比如在引用计数中 , 会出现循环引用问题 , 就是说 , 假如 : 有两个对象在堆中已经没有栈中变量的引用了(没有外部引用了) , 但是这两个对象在堆中形成了相互之间的内部引用 , 导致GC无法清除这两个循环引用的对象 , 那么这两个对象就像是在堆中的 " 孤魂野鬼 " 一样 , 孤零零的在堆中 , 那怎么收了他们呢 ? 

使用根可达分析算法就可以回收了他们 ,  判断这两个对象的是否根可达 , 然后会发现 , 这两个对象的内部虽然保持着相互引用 , 但是由于它们是根不可达的 , 所有就会被判定是垃圾对象 , 直接 GC 收掉

比如 : 下面这个循环引用 , 由于cat 和 tail 这两个引用置空了 , 则堆中的这两个对象就无法从根遍历到了 , 就成了垃圾对象 , 即可被回收

 

Minor GC 和 Full GC 的区别

普通GC(minor GC):只针对新生代区域的 GC , 指发生在新生代的垃圾收集动作 ,因为大多数 Java对象存活率都不高 ,所以 Minor GC 非常频繁 ,一般回收速度也比较快

全局GC(major GC or Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但并不是绝对的)。Full GC的速度一般要比Minor GC慢上10倍以上

1. 复制算法 (Copying)


年轻代中使用的是 Minor GC , 这种 GC 算法采用的是复制算法 ( Copying )

注意 : 复制算法是要复制存活对象到另一块区域 , 所以在根可达算法发现存活对象后 , 是直接将其复制到另一块区域的,也就是说 : 根可达分析过程中就已经完成了筛选(复制),待复制完成后,直接清理掉另一块区域即可,所以没有标记的必要

 

年轻代中的GC , 主要是复制算法(Copying)

  1. HotSpot JVM 把新生区分为了三部分:1个 Eden 区和 2个 Survivor 区(分别叫 From 和 To )。默认比例为 8 : 1 : 1 , 一般情况下,新创建的对象都会被分配到 Eden 区 (一些大对象特殊处理) , 这些对象经过第一次 Minor GC 后 ,如果仍然存活 ,将会被移到 Survivor 区。对象在 Survivor 区中每熬过一次 Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。因为新生区的对象存活率非常低 , 存活对象非常少,所以在年轻代的垃圾回收算法使用的是复制算法
  2. 复制算法的基本思想就是将内存分为两块 ( From + To 两块 ) ,每次只用其中一块 ,当这一块内存用完 ,就将还存活着的对象复制到另外一块上面。复制算法不会产生内存碎片

注意 :

设置两个survivor 区不是为了解决碎片问题 , 而是为了不让产生内存碎片

既然说到了内存碎片问题 以及 Survivor 分区 顺便也来探讨一下 ! ! ! ! !

假设 : 如果只有一个 survivor 区 会怎么样 ? ? ?  ( 会产生内存碎片 )

超详细的图解分析 : 

1. 当 Eden 区发生第一次 YGC 时 : 第一次GC , Survivor 中没有出现内存碎片 ,  但是接着看下面 .......

 2. 当 Eden 区中又产生新的对象 , 并且要发生第二次 YGC 时 :

 

由此可见 : 使用一个 survivor 区会出现内存碎片 , 导致堆空间中分布的不连续的内存片段 , 使得堆中内存空间没有充分的利用 , 严重影响 JAVA 程序的性能

所以 : 设置两个 survivor 区 ! ! !  (不让出现内存碎片)

 

可以看出使用两块 Survivor 区 , 复制之后 , 对象都是连续的占用了堆中的内存空间 , 不会产生内存碎片 , 使得堆空间充分的利用

复制算法的缺点

  1. 它浪费了 To 区的内存。就是说每次都有一块空间是空的 , 为了储备存活的对象 , 有10%的空间没有使用到
  2. 如果对象的存活率很高 ,假设是100%存活 ,那么我们需要将所有对象都复制一遍 ,这一工作所花费的时间 ,会因为对象存活率达到一定程度时,将会变的不可忽视。 所以复制算法要想使用 ,要保证对象的存活率要非常低才行 ,而且最重要的是 ,我们必须要克服 To 区的内存浪费 ( 总有百分之10的内存一直处于空闲 )

 

2. 标记清除 ( Mark - Sweep )


老年代一般是由标记清除或者是标记清除与标记整理混合实现

标记 - 清除算法

 

  1. 标记清除算法就是当程序运行期间 , 若正在使用的内存被耗尽时 , GC线程就会被触发 , 并将程序暂停 , 随后将要回收的对象标记一遍 , 最后统一回收这些对象
  2. 完成标记清理工作之后 , 便让应用程序恢复运行
  3. 标记清除见名知意 : 主要进行两项工作 , 第一项是标记 , 第二项是清除
    1. 标记 : 从引用根节点开始遍历标记 , 先标记出要回收的对象
    2. 清除 : 遍历整个堆 , 把标记的对象清除

注意 : 这个标记不一定是标记的存活对象 , 也可以标记需要回收的对象 , 参考周志明老师的 < 深入 Java 虚拟机第三版 > 可知 : 这个标记过程也就是说根可达 , 还是根不可达 最主要是为了判定此对象是否属于垃圾对象 : 如下图所示 :

 

标记清除的缺点

  1. 首先 , 它的缺点就是效率比较低 , 因为无论是标记还是清除 , 都需要进行全堆的遍历 , 标记和清除两个过程的执行效率都随对象数量增长而降低 , 而且在进行GC的时候,需要停止应用程序,这会导致用户体验非常差劲
  2. 通过这种方式清理出来的空闲内存是不连续的 , 我们的要回收的对象都是随机的出现在内存的各个位置的 ,现在把它们清除之后 ,内存的布局自然会乱七八糟 , 产生断断续续的内存片段 , 内存空间不能得到充分的使用 , 而且有可能会出现很糟糕的情况 : 这时突然分配了一个大内存对象 , 由于内存空间断断续续的 , 这个大对象就无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

3. 标记压缩 ( 标记整理 ) ( Mark-Compact )


老年代一般是由标记清除或者是标记清除与标记整理的混合实现

复制算法是建立在存活对象少 , 垃圾对象多的情况下的 , 这种情况新生代经常发生 , 但是在老年代 , 更常见的情况是大部分对象都是存活的对象 , 如果还使用复制算法 , 存活的对象很多 ,这样导致复制的成本也会很高 , 因此复制算法不适用于老年代

可以看到标记的对象将会被整理 , 按照内存地址依次排列 ( 这样的连续内存片段 , 就不会产生碎片化 ) ,未被标记的内存就会被清理掉 , 这样 , 我们需要给新对象分配内存时 , JVM只需要一个内存的起始地址即可 , 和复制算法相比 : 要维护一整个空闲列表 , 节省了许多开销

标记整理算法 : 弥补标记清除算法中内存区域分散的缺点,也消除了复制算法当中内存 To 空间浪费的高额代价

参考周志明老师的 < 深入理解 Java 虚拟机第三版 > " 标记 - 整理 算法 "

 

标记 - 整理算法 的缺点

  1. 标记 / 整理 算法唯一的缺点就是效率也不高 ,不仅要标记对象 ,还要整理存活对象 , 清除垃圾对象。
  2. 移动存活对象并更新所有的引用会是一种极为负重的操作,而且这种对象移动操作必须全程暂停用户应用程序 (STW) 才能进行 , 从效率上来说 ,标记 / 整理算法要低于复制算法。

总结:

内存效率 (速度) : 复制算法 > 标记清除算法 > 标记整理算法

内存整齐度:复制算法 = 标记整理算法 > 标记清除算法

内存利用率:标记整理算法 = 标记清除算法 > 复制算法

从效率上来说,复制算法非常强大,但是浪费了太多内存,那么要找到一个折中的算法 , 标记 / 整理算法相对来说比较适中一些,但是呢 , 效率有点低 ,它比复制算法多了一个标记的阶段,又比标记 / 清除多了一个整理内存的过程

难道就没有一种最优算法吗?

答案是: 没有滴 , 具体情况具体分析 , 没有最好的算法 , 只有最合适的算法 

-------------------------  分代收集算法 ( 不同的区域使用不同的收集算法 )  ----------------------

年轻代 ( Young Gen )

年轻代特点是区域相对老年代较小,对象存活率低 , 这种情况使用复制算法,速度是最快的 , 效率很高

老年代 ( Tenure Gen )

老年代的特点是区域较大,对像存活率高 。

这种情况 ,存在大量存活率高的对像,复制算法明显变得不合适。老年代一般是由标记清除或者是标记清除与标记整理的混合实现

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值