JVM详解-垃圾回收机制

目录

垃圾收集算法

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

2、复制算法

3、标记-整理算法

JVM垃圾收集器  

1、Serial

2、ParNew

3、Parallel Scavenge 收集器

4、Serial Old 收集器

5、Parallel Old 收集器

6、CMS(Concurrent Mark Sweep)收集器

7、G1(Garbage-First)收集器

JVM 垃圾回收机制


垃圾收集算法

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

        首先标记处所有需要回收的对象,在标记完成后统一回收。缺点:标记和清除两个过程都效率低;标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行中需要分配大对象时,无法找到足够的连续内存而不得不提取触发 GC。

2、复制算法

        将可用内存按容量划分成大小相等的两块,每次只使用一块。当这一块使用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存一次清理掉。这样不用考虑内存碎片的问题,只要移动堆顶指针,按顺序分配即可,实现简单、运行高效。缺 点:内存缩小为原来的一半。

        现代商用虚拟机都采用这种算法回收新生代。而新生代中约 98%的对象都是“朝生夕死”,所以不需按 1:1 划分。HotSpot 默认 Eden 和 Survivor 是 8:1,所以每次可用内存为90%。但我们没法保证每次回收只有不多于 10%的对象存活,当 Survivor 空间不够时,需要依赖其他内存(这里指老年代)进行分配担保(直接进入老年代)。

         缺点:如果对象存活率太高,要进行较多复制操作,效率低。且需要额外空间担保,老年代不能选用这种算法。

3、标记-整理算法

         过程与“标记-清除”一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。老年代因为对象存活率高、没有额外空间进行分配担保,必须使用“标记-清理”或“标记-整理”算法。

JVM垃圾收集器  

1、Serial

        是一个单线程收集器,在它进行垃圾收集时,必须暂停其他所有工作线程(StopThe World);简单高效,是虚拟机在 Client 模式下默认的新生代收集器(复制算法)。停顿时间在几十到一百多毫秒以内,可以接受。

 

2、ParNew

        其实就是 Serial 收集器的多线程版本;ParNew 收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器。除去性能因素,很重要的原因是除了Serial 收集器外,目前只有它能与 CMS 收集器(老年代)配合工作。(复制算法)

        但是,在单 CPU 环境中,ParNew 收集器绝对不会有比 Serial 收集器更好的效果,甚至由于存在线程交互的开销,该收集器在通过超线程技术实现的两个 CPU 的环境中都不能百分之百地保证可以超越 Serial 收集器。然而,随着可以使用的 CPU 的数量的增加,它对于GC时系统资源的有效利用还是很有好处的。

 

3、Parallel Scavenge 收集器

        是新生代垃圾收集器,使用复制算法,也是并行的多线程收集器。与 ParNew 收集器相比,很多相似之处,但是 Parallel Scavenge 收集器更关注可控制的吞吐量(运行用户代码时间/(运行用户代码+垃圾收集时间))。吞吐量越大,垃圾收集的时间越短,则用户代码则可以充分利用 CPU 资源,尽快完成程序的运算任务。

        直观上,只要最大的垃圾收集停顿时间越小,吞吐量是越高的,但是GC 停顿时间的缩短是以牺牲吞吐量和新生代空间作为代价的。比如原来 10 秒收集一次,每次停顿100 毫秒,现在变成 5 秒收集一次,每次停顿 70 毫秒。停顿时间下降的同时,吞吐量也下降了。

4、Serial Old 收集器

        是 Serial 收集器的老年代版本,也是一个单线程收集器,采用“标记-整理算法”进行回收。其运行过程与 Serial 收集器一样。

        Serial Old 收集器的主要意义也是在于给 Client 模式下的虚拟机使用。如果在Server模式下,那么它主要还有两大用途:一种用途是在 JDK 1.5 以及之前的版本中与ParallelScavenge 收集器搭配使用,另一种用途就是作为 CMS 收集器的后备预案,在并发收集发生Concurrent Mode Failure 时使用。

5、Parallel Old 收集器

        是 Parallel Scavenge 收集器的老年代版本,使用多线程和“标记-整理”算法进行垃圾回收。其通常与 Parallel Scavenge 收集器配合使用,“吞吐量优先”收集器是这个组合的特点,在注重吞吐量和 CPU 资源敏感的场合,都可以使用这个组合。

6、CMS(Concurrent Mark Sweep)收集器

        是一种以获取最短回收停顿时间为目标的收集器,基于“标记-清除”算法,从总体上来说,CMS 收集器的内存回收过程是与用户线程一起并发执行的(有的过程也是 StopTheWorld)

         CMS 分为四个步骤:初始标记(GCRoots 能直接关联到的对象,速度快,可达性分析,Stop The World),并发标记(可达性分析),重新标记(修正并发标记期间因用户程序继续运作而导致的变动,速度快,Stop The World),并发清除

         CMS 的优点很明显:并发收集、低停顿。由于进行垃圾收集的时间主要耗在并发标记与并发清除这两个过程,虽然初始标记和重新标记仍然需要暂停用户线程,但是从总体上看,这部分占用的时间相比其他两个步骤很小,所以可以认为是低停顿的。

        缺点:

        对 CPU 资源太敏感,这点可以这么理解,虽然在并发标记阶段用户线程没有暂停,但是由于收集器占用了一部分 CPU 资源,导致程序的响应速度变慢

        CMS 收集器无法处理浮动垃圾。所谓的“浮动垃圾”,就是在并发标记阶段,由于用户程序在运行,那么自然就会有新的垃圾产生,这部分垃圾被标记过后,CMS 无法在当次集中处理它们(为什么?原因在于 CMS 是以获取最短停顿时间为目标的,自然不可能在一次垃圾处理过程中花费太多时间),只好在下一次 GC 的时候处理。这部分未处理的垃圾就称为“浮动垃圾”。由于垃圾收集阶段用户线程还需要运行,那就不能等老年代几乎全满了再收集,一般达到 92%时就开始收集,而 CMS 运行期间预留的内存无法满足程序需要,就会出现“Concurrent Mode Failure”,此时将启动备用方案 serial old

        由于 CMS 收集器是基于“标记-清除”算法的(可能是为了时间短),前面说过这个算法 会导致大量的空间碎片的产生,一旦空间碎片过多,大对象就没办法给其分配内存,那么即使内存还有剩余空间容纳这个大对象,但是却没有连续的足够大的空间放下这个对象,所以虚拟机就会触发一次 Full GC。

        在使用 CMS 收集老年代时,新生代只能选用 ParNew 或者 Serial 收集器中的一个(CMS与其他不配套,其他的没有使用传统的 GC 收集器框架)

 

7、G1(Garbage-First)收集器

        JDK1.7 才开始商用。使用 G1 收集器时,Java 堆内存布局与其他收集器有很大差别,它将整个 Java 堆分为多个大小相等的独立区域(Region),虽然还保留新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,他们都是Region(不需要连续)的集合。

         特点:并行与并发。分代收集(不需要其他收集器配合)。空间整合(整体来看采用“标记-整理”,局部(两个 Region 之间)采用复制)。可预测的停顿。

        G1 跟踪各个 Region 里面的垃圾堆积价值大小(回收所获得的空间大小以及回收所需的时间),在后台维护一个优先列表,每次优先收集价值最大的 Region(所以叫Garbage-First),从而保证了 G1 在有限时间内可以获取尽可能高的收集效率。

        (老年代)过程:初始标记(Stop The World)、并发标记、最终标记(Stop The World)、筛选回收(Stop The World

        G1 的 YoungGC 就是将 E 区和 S 区复制到灰色的空白区。

        G1 中有 Humongous 区(巨大区)用于存放比标准块大 50%的对象

 

JVM 垃圾回收机制

        在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集(有 eden 和survivor 供复制,有老年代最分配担保)。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或者“标记-整理”算法来进行回收。

发生 Minor GC,采用复制算法,发现

        1、 复制对象无法全部放入 Survivor,只好通过分配担保机制提前转移到老年代中

        2、 大对象(长字符串或长数组等需要大量连续空间的对象)直接进入老年代(防止大对象在                eden 和 Survivor 中经常复制)通过-XX:PretenureSizeThreshold 参数设置如                3MB,大于这个参数的直接进入老年代

        3、 长期存活对象进入老年代(默认 15 岁)

Minor GC:新对象先放入 eden 区,当 eden 满了会触发 Minor GC。

Full GC(等于 Major GC):

        1、每次进行 Minor GC 时,JVM 会计算 Survivor 区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次 Full GC

         2、老年代空间不足时触发 Full GC,只有在新生代对象转入或创建为大对象、大数组时才会出现不足的现象(大对象直接进入老年代),分配担保

        3、永久代满(永久代 JDK8 被移除)

        优化 Full GC 本身不会先进行 Minor GC,我们可以配置,让 Full GC 之前先进行一次Minor GC,因为老年代很多对象都会引用到新生代的对象,先进行一次Minor GC 可以提高老年代 GC 的速度。

        在 jvm 分带垃圾回收机制中,将应用程序可用的堆空间分为年轻代和老年代,又将年轻代分为 eden 区、from 区、to 区,新建对象总是在 eden 区中被创建,当eden 区空间已满,就触发一次 Minor gc,将还被使用的对象复制到 from 区,这样整个eden 区都是未被使用的空间,可供继续创建对象,当 eden 区再次用完,再触发一次 Minor gc,将eden 区和from区还在被使用的对象复制到 to 区,下一次 Minor gc 则是将 eden 区和to 区还被使用的对象复制到 from 区。因此,经过多次 Minor gc,某些对象会在 from 区和to 区多次复制,如果超过某个阈值对象还未被释放,则将对象复制到老年代。如果老年代空间也已用完,那么就会触发 full gc,即所谓的全量回收。

         永久代的垃圾回收主要有两部分:废弃常量和无用的类。如没有任何String 对象引用“abc”。在大量使用反射、动态代理、CGlib 等 ByteCode 框架,动态生成JSP 以及OSGi这类频繁自定义 ClassLoader 的场景都需要虚拟机具备类卸载功能(回收永久代),以保证永久代不会溢出。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值