JVM GC算法和常见的垃圾收集器

JVM GC算法和常见的垃圾收集器

垃圾收集算法

标记-清除算法(Mark-Sweep)

标记-清除算法是最基础的算法,分为标记和清除两个阶段:

  • 首先要标记出需要清除回收的对象
  • 在标记完成后统一回收所有被标记的对象
    它有两个缺点:
  • 效率问题,标记和清除效率都不高
  • 空间问题,清除后会产生大量不连续的存储空间碎片,空间碎片太多可能导致程序在后面运行过程中需要分配较大对象无法找到满足的连续内存不得不提前触发一次垃圾收集动作。

复制算法(Copying) 针对新生代

为了解决标记-清除算法效率问题,出现了复制算法。它可以将内存划分为大小相等的两块,每次使用其中一块。当这块用完了,就将还存活的对象复制到另一块,然后再把已经使用的内存空间一次清理掉。优点是每次都对其中一块进行内存回收,内存分配就不用考虑内存碎片的问题,只需要移动到堆顶指针,按顺序分配内存实现简单运行效率高。缺点是将内存缩小为原来的一半,代价太高。

现在商用虚拟机都采用复制算法来回收新生代,有研究表明,新生代的对象有98%都是朝生夕死,所以不用按1:1的比例分配内存空间,而是将一大块分配给Eden区域和两块较小的Survivor区域。每次使用Eden和一个Surviror区域,当回收时将Eden和Surviror区域还存活的对象移动到另外一块Surviror区域,最后清理Eden和刚才使用过的Surviror区域。**HotSpot虚拟机默认按照8:1的比例分配Eden和Surviror区,也就是说每次新生代可以存放的对象的容量是整个新生代的90%的空间,只有10%的空间会被浪费。当然,不能每次回收只剩下10%的对象,如果Surviror空间不足需要其他内存(老年代)进行担保分配(Handle Promotion)。即如果另外一块Surviror空间没有足够的空间存放上次遗留的对象,这些对象通过担保分配机制进入老年代。

标记-整理算法(Mark-Compact)–针对老年代

复制收集算法在对象存活率较高时就需要执行较多的复制操作,效率会变低;更关键的是,如果不想浪费50%的存储空间,就需要额外的空间进行分配担保,以应对被使用的内存中对象100%存活的极端情况,所以老年代一般不直接不选用复制收集算法。
根据老年代的特点提出了标记-整理算法,标记过程中与标记-清除算法一致,但后续步骤不是直接回收对象进行清理而是让所有存活的对象都向一端移动,然后清理掉边界意外的内存。

标记-整理的步骤:
  • 标记阶段
  • 整理阶段:移动存活的对象,同时更新对存活对象中所有指向被移动对象的指针
整理顺序

不同算法中,遍历层次的次数、整理顺序、对象迁移方式都有所不同;而整理顺序又会影响到程序的局部性。主要有一下三种顺序:

  • 任意顺序:对象的移动方式和它们初始的对象排列及引用关系无关。
       任意顺序整理实现比较简单且执行速度快,但是任意顺序可能把原来两个相邻的对象打乱到不同的高速缓存行或虚拟内存页中会降低赋值器的局部性。任意顺序算法只能处理单一大小的对象或针对不同大小的对象需要分批处理。
  • 线形顺序:将具有关联性的对象排列在一起
  • 滑动顺序:将对象“滑动”到堆的一端,从而“挤出”垃圾,可以保持对象在原有的顺序。
      所有现代的标记-整理回收器均使用滑动处理,它不会改变对象的相对顺序,也不会影响赋值器的空间局限性。复制式回收器甚至可以通过改变对象布局方式,将对象与父节点或兄弟节点排列的更近以提供赋值器的空间局限性。
      整理算法的限制,如果整理过程中需要2次或者3次遍历堆空间,对象头部可能需要一个额外的槽来保存迁移信息。
部分整理算法
  • 双指针回收算法:实现简单且速度快但会打乱对象原有的布局
  • Lisp2(滑动回收算法):需要在对象对象头用一个额外的槽保存迁移完的地址
  • 引用整理算法:可以在不引用额外的存储空间开销的情况下实现滑动整理但需要2次遍历,遍历成本高
  • 单次遍历算法:滑动回收,实时计算出对象的转发地址而不需要额外的开销

分代收集算法(Generational Collection)

  当代商业虚拟机的垃圾收集器都采用“分代收集”算法,这种算法并无新的方法;只是根据对象的存活周期不同将堆内存划分为几块,一般是把堆分为新生代和老年代;这样就可以根据不同的分区的特点使用不同的分区算法。新生代中,每次都会发现大批量的对象死去,只有少量存活,那就用复制算法,只需要付出少量的复制成本就可以完成收集;针对老年代存活率比较高、没有额外的存储空间进行担保,则使用“标记-清理”或“标记-整理”算法进行回收。

常见的垃圾收集器

    如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现。下图展示7种垃圾收集器;如果两个收集器之间存在连线,就说明他们可以搭配使用。并没有最好的垃圾收集器,我们需要针对具体的使用场景选择适合的垃圾收集器

Serial收集器(用于新生代)

单线程,在进行垃圾收集时必须停掉其他的工作线程(“Stop The World”)。虚拟机运行在Client模式下默认新生代的垃圾收集器。简单而高效,针对限定CPU来说,Serial收集器没有线程间切换的开销,专心做垃圾收集自然获得高的单线程效率。

ParaNew收集器(新生代)

ParNew是Serial收集器的多线程版本,他是运行在Server模式下虚拟机中首选的新生代收集器,除了Serial收集器外,它只与CMS收集器配合工作。

Parallel Scavenge收集器(新生代)–吞吐量优先

使用复制算法,并行多线程,这些与parNew一样,它的独到之处是他的关注点与其他收集器不同。CMS收集器的关注点是尽可能缩短用户线程停顿的时间,而Parellel Scavenge关注的是达到一个可控制的吞吐量,即CPU用于运行用户代码的时间与CPU消耗的总时间的比值。吞吐量=运行用户代码时间/(运行代码时间+垃圾收集时间)。
停顿时间越短对于需要与用户交互程序来说,良好的响应速度能提升用户的体验。
高吞吐量可以高效的利用CPU时间,尽快完成程序的运算任务,主要适合后台运算量大不太需要过多的交互的任务。

常用参数设置
  • MaxGCPauseMills:控制最大垃圾收集时间。大于0的毫秒数,停顿时间缩短是以牺牲吞吐量和新生代空间换取的;新生代调的小,吞吐量跟着小,垃圾收集时间就越短,停顿就小。
  • GCTimeRatio:设置吞吐量大小,0<x<100;
  • UseAdaptiveSizePolicy:一个开关参数,开启GC自适应调节策略,将内存管理的调优任务(新生代大小、Eden,Surviror的比例SurvivorRatio、晋升老年代年龄PretenureSizeThreshold等参数)交给虚拟机完成。这是Parallel Scavenge收集器与ParNew一个重要的区别。

Serial Old(老年代)

它是Serial收集器老年代版本,单线程,使用标记-整理算法。主要应用于在Client模式下的虚拟机。如果在Server模式下,它有两大用途:

  • 在JDK1.5之前与Parallel Scavenge收集器搭配使用
  • 作为CMS收集器后预备案,在并发收集发生Concurrent Mode Failure的时候使用。运行过程同Serial一样。

Parallel Old(老年代)

它是Parallel Scavenge在老年代的版本,多线程,使用标记-整理算法。在注重吞吐量和CPU敏感的场合,都可以考虑使用Parallel Scavenge+Parallel Old收集器

CMS收集器(Concurrent Mark Sweep)

它是一种获取最短回收停顿时间为目标的收集器,优点:并发收集,低停顿。基于标记-清除算法。目前有很大一部分Java应用集中在互联网或BS系统服务上,这类尤其重视响应速度希望系统停顿时间短以带来最好的用户体验;CMS收集器就是最符合这类需求,运作比较复杂,具体如下:

  1. 初始标记(CMS initial Mark):需要“stop the world”,标记GC Roots能直接关联的对象,速度快。
  2. 并发标记(CMS concurrent Mark):进行GC Roots Tracing过程。
  3. 重新标记(CMS remark):需要“stop the world",修正并发标记期间因用户程序运行而导致的标记产生变动的那一部分对象的标记记录。停顿时间:初始标记<重新标记<并发标记
  4. 并发清除(CMS concurrent sweep):时间较长
缺点
  1. 对CPU资源非常敏感,CMS默认回收线程:(CPU数量+3)/4
  2. 无法处理浮动垃圾,可能出现“Concurrent Mode Failure"失败而导致一次FULL GC。并发清除阶段用户程序产生的垃圾过了标记阶段所以无法在本次垃圾收集中收集-成为浮动垃圾。CMS默认在老年代使用68%的空间后会被激活,若老年代增长的不是很快可以适当的调高参数 -XX:CMSInitiatingOccunpancyFraction 提高触发百分比,但调的过高会导致"Concurrent Mode Failure"出现
  3. 基于标记-清除算法会产生大量的碎片。提供参数 -XX:+UseCMSCompactAtFullCollection 用于享受完FULL GC后进行碎片整理,内存碎片整理过程是无法并发的但停顿时间过长。可以通过参数 -XX:CMSFullGCBeforeCompaction设置执行多少次不压缩的FULL GC后再来一次内存压缩清理碎片空间。

G1收集器(Garbage First):

它是当前收集器技术发展的最前沿成果。与CMS相比有两个显著改进:

  • 基于“标记-整理”算法实现收集器
  • 非常精确地控制停顿。

G1收集器可以在几乎不牺牲吞吐量的前提下完成低停顿的内存回收,这是由于它能够极力避免全区域的垃圾收集,之前的收集器进行收集的范围都是整个新生代或老年代,而G1将整个Java堆(包括新生代、老年代)划分为多个大小固定的独立区域(Region),并且跟踪这些区域里面的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃圾最多的区域(这就是Garbage First名称的由来)。区域划分、有优先级的区域回收,保证了G1收集器在有限的时间内可以获得最高的收集效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值