垃圾收集算法与垃圾收集器

垃圾收集算法与垃圾收集器

  1. 标记-清除算法

    最基础的算法,分为标记和清除两个阶段:首先标记需要清除的对象,在标记完成后统一回收所有被标记的对象。它有两个缺点:一个是效率问题:标记和清除的效率不高;一个是空间问题:清除之后会产生大量的碎片问题,导致再分配大对象内存时由于找不到足够的连续内存而再一次触发垃圾回收。

  2. 复制算法

    为了解决效率问题,出现了复制算法,它可以将内存分为大小相等的两块,每次只使用其中的一块。当一块内存用完了,将还存活的对象复制到另一块上面,然后再将刚刚用完的那块内存一次性清除掉。这样就解决了内存碎片问题。缺点是使内存容量降为原来的一半。

  3. 标记-整理算法

    复制算法在对象存活率较高时会频繁的复制对象,效率降低。因此又出现了标记-整理算法,标记过程同标记-清除算法。只不过不是直接将对象清除掉,而是将还存活的对象像一侧移动,然后将边界以外的对象清除掉。

  4. 分代收集算法

    当前商业虚拟机都是采用分代收集算法,并不是新的算法,而是根据对象的生存周期将对象分为:新生代和老年代,方法区成为永久代(在新的版本中已经将永久代废弃而引入了元空间的概念,永久代使用的是jvm的内存而元空间直接使用物理内存)。

    新生代的对象的生命周期非常短,在GC时会有大量的对象死去,少量存活,所以使用复制算法,新生代又分为Eden区和Survior区(survior from 和 Survivor to),默认大小比例为8:1:1。

    老年代中因为对象的存活率高、没有额外的内存空间作为担保,使用标记-清除算法或者标记-整理算法。

    • 新产生的对象优先进入eden区,当eden区满了之后再使用Survivor from,当Survivor from也满了之后就进行Minor GC(新生代GC),将eden区Survivor from中存活的对象copy到Survivor to,然后清空eden区Survivor from区,这个时候原来的Survivor from就变成了Survivor to,原来的Survivor to变成了Survivor from。如果Survivor to无法容纳所有存活下来的对象,则根据老年代的分配担保将对象copy到老年代中,如果老年代也无法容纳则进行Full GC(老年代GC)。

    • 大对象直接进入老年代:jvm中有个参数配置-XX:PretenureSizeThreshold,另大于这个设置值的对象直接进入老年代,目的是为了避免在eden区和Survivor区发生大量的内存复制。

    • 长期存活的对象直接进入老年代:jvm为每一个对象定义一个年龄计数器,如果对象在eden区经过第一次Minor GC仍然存活进入Survivor区则设置起年龄为1,每熬过一次Minor GC就会升级一次,年龄+1,当年龄到达一定程度,(默认是15,可通过XX:MaxTenuringThreshold来设定),就会移入到老年代。但是Jvm并不是必须要求年龄到达最大年龄后才能进入老年代,如果Survivor区中的的相同年龄(如10)的所有对象的总和大于Survivor的一半,年龄大于10的所有对象直接进入老年代

垃圾收集器

垃圾收集算法是方法论,垃圾收集器是具体的实现,jvm规范对于垃圾收集器应该如何实现没有规定,因此不同的虚拟机实现不同,我们只看hotSpot虚拟机。

  1. Serial收集器

    Serial收集器是最基本、历史最久的收集器,曾是新生代手机的唯一选择。他是单线程的,只会使用一个CPU或一条收集线程去完成垃圾收集工作,并且它在收集的时候,必须暂停其他所有的工作线程,直到它结束,即“Stop the World”。停掉所有的用户线程,对很多应用来说难以接受。

  2. ParNew收集器

    ParNew收集器是Serial收集器的多线程版本,除了使用了多线程之外,其他的行为(收集算法、stop the world、对象分配规则、回收策略等)同Serial收集器一样。

  3. Parallel Scavenge收集器

    新生代收集器,并行的多线程收集器。它的目标是达到一个可控的吞吐量(就是CPU运行用户代码的时间与CPU总消耗时间的比值,即 吞吐量=行用户代码的时间/[行用户代码的时间+垃圾收集时间]),这样可以高效率的利用CPU时间,尽快完成程序的运算任务,适合在后台运算而不需要太多交互的任务。

  4. Serial Old收集器

    Serial 收集器的老年代版本,单线程,“标记整理”算法,主要是给Client模式下的虚拟机使用。

  5. CMS收集器

    CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,停顿时间短,用户体验就好。

    基于“标记清除”算法,并发收集、低停顿,运作过程复杂,分4步:

    1. 初始标记:仅仅标记GC Roots能直接关联到的对象,速度快,但是需要“Stop The World”
    2. 并发标记:就是进行追踪引用链的过程,可以和用户线程并发执行。
    3. 重新标记:修正并发标记阶段因用户线程继续运行而导致标记发生变化的那部分对象的标记记录,比初始标记时间长但远比并发标记时间短,需要“Stop The World”
    4. 并发清除:清除标记为可以回收对象,可以和用户线程并发执行
      由于整个过程耗时最长的并发标记和并发清除都可以和用户线程一起工作,所以总体上来看,CMS收集器的内存回收过程和用户线程是并发执行的。
  6. G1收集器

G1(Garbage-First)是JDK7-u4才正式推出商用的收集器。G1是面向服务端应用的垃圾收集器。它的使命是未来可以替换掉CMS收集器。

G1收集器特性:

  1. 并行与并发:能充分利用多CPU、多核环境的硬件优势,缩短停顿时间;能和用户线程并发执行。分代收集:G1可以不需要其他GC收集器的配合就能独立管理整个堆,采用不同的方式处理新生对象和已经存活一段时间的对象。空间整合:整体上看采用标记整理算法,局部看采用复制算法(两个Region之间),不会有内存碎片,不会因为大对象找不到足够的连续空间而提前触发GC,这点优于CMS收集器。可预测的停顿:除了追求低停顿还能建立可以预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不超N毫秒,这点优于CMS收集器。
    为什么能做到可预测的停顿?

  2. 是因为可以有计划的避免在整个Java堆中进行全区域的垃圾收集。G1收集器将内存分大小相等的独立区域(Region),新生代和老年代概念保留,但是已经不再物理隔离。G1跟踪各个Region获得其收集价值大小,在后台维护一个优先列表;每次根据允许的收集时间,优先回收价值最大的Region(名称Garbage-First的由来);这就保证了在有限的时间内可以获取尽可能高的收集效率。

猜你喜欢

转载自blog.csdn.net/zongyeqing/article/details/80874115