天天看点

【Java虚拟机】垃圾回收算法

本文部分内容引用自 https://www.cnblogs.com/java-zhao/p/5183261.html

1、三种垃圾回收算法

  1. 标记-整理法(标记压缩)(老年代)
  2. 标记-清除法(老年代)
  3. 复制算法(新生代)

1.1 标记-清除法

【Java虚拟机】垃圾回收算法

原理:

从根节点集合出发进行扫描,标记出存活的对象,最后扫描整个内存空间并清除没有标记的对象(即死亡对象)

适用场景:

  • 对象存活比较多的情况下比较高效
  • 适用于老年代

缺点:

  • 容易产生内存碎片,如果此时再为一个较大的对象分配内存时,会提前触发垃圾回收
  • 扫描了整个磁盘空间两次(第一次标记存活对象,第二次清除没有标记的对象)

1.2 标记-整理法

【Java虚拟机】垃圾回收算法

原理:

从根节点集合出发,标记出存活的对象,最后扫描整个内存空间,清除未被标记的对象,清除完毕之后,将所有存活的对象左移到一起。

适用场景:

  • 用于老年代

缺点:

  • 需要移动对象,若对象非常多且标记回收后的内存非常不完整,可能移动这个动作也要花费一定的时间
  • 扫描了整个内存空间两次(一次标记,一次清除)

优点:

  • 不会产生内存碎片

1.3 复制算法

【Java虚拟机】垃圾回收算法

原理:

从根集合进行扫描,标记出所有的存活对象,并且将这些存活对象复制到一块新的内存 区域,然后将原内存空间中的对象全部回收。

适用场景:

  • 存活对象较少时比较高效
  • 扫描了整个内存空间一次(标记复活对象并且移动)
  • 适用于新生代:98%的对象就是朝生夕死,存活下来的很少

缺点:

  • 需要一块空的内存区域
  • 需要复制移动对象

1.4 分代收集算法

概述:

当前的商用虚拟机都采用“分代收集”算法,分代收集算法并没有什么新的思想。

原理:

根据对象的存活周期的不同将内存划分为新生代和老年代这样就可以根据各个年代的特性采用最适当的收集算法。新生代中每次垃圾收集都有大量的对象死去,存活的对象只占少数,因此新生代采用复制算法,只要付出少量的复制成本就可以完成收集。而老年代中因为对象的存活率较高、没有额外的空间对他进行分配担保,就必须使用“标记-清除”或者“标记-整理”算法来进行回收。

详解:

  1. 一般将Java堆分为新生代和老年代,除此之外,新生代又进一步划分为Eden区,和Survivor区,Survivor区又被划分为FromSurvivor 和To Survivor区。在大部分虚拟机中,Eden:From:To = 8:1:1。
  2. 产生的对象优先分配在Eden区内。当Eden区的剩余内存不足以为新的对象分配内存,或Eden区已满时,会触发MinorGC,本次GC会将Eden区中存活的对象复制到FromSurvivor区,如果发现存活下来的对象From区放不下,那么这些存活下来的对象会通过分配担保机制全部进入老年代,之后将Eden区全部回收掉。
  3. 之后产生的对象依然被分配在Eden区,下次Eden区满了或者放不下时,会将Eden区和From区中存活的对象全部复制到To区,同理,如果To区的内存大小放不下这些存活的对象,那么这些存活的对象会通过分配担保机制全部进入老年代,即每次进行GC时,From和To区总有一块区域空闲。
  4. 当碰到Eden区放不下的大对象(指那些需要大量连续内存的对象)时,会直接将大对象放入老年代。这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存复制,这样会降低复制的效率。
  5. 虚拟机为每个对象定义了一个对象年龄计数器,每“平安度过”一次GC,并且能被Survivor区所容纳的话,就将计数器加一。当对象的年龄增加到一定程度(默认为15岁),此对象就会晋升到老年代中。当然,为了更好的适应不同程序的内存情况,虚拟机并不是永久得要求对象的年龄必须达到最大年龄限制才能晋升到老年代。如果在Survivor空间中相同年龄的对象大小的总和超过Survivor空间内存大小的一半时,年龄大于等于改年龄数值的对象会直接晋升到老年代中去,无需等到最大年龄限制。

2、两种GC的区别

新生代GC(MinorGC):指发生在新生代的垃圾回收动作,因为大多数Java对象都具备朝生夕灭的的特性,所以Minor GC非常频繁,一半回收速度也比较快。

老年代GC(Major GC/Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的MinorGC。Major GC 的速度一半会比Minor GC慢10倍以上。