最新消息:从今天开始,做一个有好习惯的人。

垃圾回收器

java体系 迷路的老鼠 4438浏览 2评论

什么是垃圾回收器

Java垃圾回收器是Java虚拟机(JVM)的三个重要模块(另外两个是解释器和多线程机制)之一,为应用程序提供内存的自动分配(Memory Allocation)、自动回收(Garbage Collect)功能

内存的分配和回收都发生在Java堆上(一段内存块)。某一个时点,一个对象如果有一个以上的引用(Rreference)指向它,那么该对象就为活着的(Live),否则死亡(Dead),视为垃圾,可被垃圾回收器回收再利用。垃圾回收操作需要消耗CPU、线程、时间等资源,所以容易理解的是垃圾回收操作不是实时的发生(对象死亡马上释放),当内存消耗完或者是达到某一个指标(Threshold,使用内存占总内存的比列,比如0.75)时,触发垃圾回收操作。有一个对象死亡的外,java.lang.Thread类型的对象即使没有引用,只要线程还在运行,就不会被回收。

串行回收器(Serial Collector) ,线程执行回收操作,回收期间暂停所有应用线程的执行,client模式下的默认回收器。

年轻代的回收算法(Minor Collection) ,把Eden区的存活对象移到To区,To区装不下直接移到老年代,把From区的移到To区,To区装不下直接移到老年代,From区里面年龄很大的升级到老年代。 回收结束之后,Eden和From区都为空,此时把From和To的功能互换,From变To,To变From,每一轮回收之前To都是空的。设计的选型为复制

老年代的回收算法(Full Collection),老年代的回收分为三个步骤,标记(Mark)、清除(Sweep)、合并(Compact)。标记阶段把所有存活的对象标记出来,清除阶段释放所有死亡的对象,合并阶段 把所有活着的对象合并到年老代的前部分,把空闲的片段都留到后面。设计的选型为标记整理,减少内存的碎片。

并行回收器(ParNew),ParNew回收器是一个工作在新生代的垃圾收器,他只是简单的将串行回收器多线程化,回收策略和算法和串行回收器一样。

并行回收集器(ParallelGC),ParallelOldGC回收期是一个老年代垃圾回收器,设计选型为标记整理

CMS(并发GC)收集器,也是一个老年代垃圾回收器,基于标记清理实现。

过程

  • 初始标记(CMS initial mark),独占PUC,仅标记GCroots能直接关联的对象
  • 并发标记(CMS concurrenr mark),可以和用户线程并行执行,标记所有可达对象
  • 重新标记(CMS remark) ,独占CPU(STW),对并发标记阶段用户线程运行产生的垃圾对象进行标记修正
  • 并发清除(CMS concurrent sweep) ,可以和用户线程并行执行,清理垃圾

其中初始标记、重新标记这两个步骤任然需要停顿其他用户线程。初始标记仅仅只是标记出GC ROOTS能直接关联到的对象,速度很快,并发标记阶段是进行GC ROOTS 根搜索算法阶段,会判定对象是否存活。而重新标记阶段则是为了修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间会被初始标记阶段稍长,但比并发标记阶段要短。

由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以整体来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。

优点:并发收集、低停顿

缺点

  • 对CPU非常敏感:在并发阶段虽然不会导致用户线程停顿,但是会因为占用了一部分线程使应用程序变慢;
  • 无法处理浮动垃圾:在最后一步并发清理过程中,用户线程执行也会产生垃圾,但是这部分垃圾是在标记之后,所以只有等到下一次gc的时候清理掉,这部分垃圾叫浮动垃圾;
  • CMS使用“标记-清理”法会产生大量的空间碎片,当碎片过多,将会给大对象空间的分配带来很大的麻烦,往往会出现老年代还有很大的空间但无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC,为了解决这个问题CMS提供了一个开关参数,用于在CMS顶不住,要进行Full GC时开启内存碎片的合并整理过程,但是内存整理的过程是无法并发的,空间碎片没有了但是停顿时间变长了。

CMS出现FULL GC的原因:

  • 年轻带晋升到老年带没有足够的连续空间,很有可能是内存碎片导致的
  • 在并发过程中JVM觉得在并发过程结束之前堆就会满,需要提前触发Full GC

G1,是一款面向服务端应用的垃圾收集器,也就是不需要其他收集器协助就能管理新生代和老年代内存。(JDK1.7及以上版本)

与其它收集器相比,G1变化较大的是它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留了新生代和来年代的概念,但新生代和老年代不再是物理隔离的了它们都是一部分Region(不需要连续)的集合。同时,为了避免全堆扫描,G1使用了Remembered Set来管理相关的对象引用信息。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏了。

过程

  • 初始标记(Initial Making)
  • 并发标记(Concurrent Marking)
  • 最终标记(Final Marking)
  • 筛选回收(Live Data Counting and Evacuation)

看上去跟CMS收集器的运作过程有几分相似,不过确实也这样。初始阶段仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS(Next Top Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可以用的Region中创建新对象,这个阶段需要停顿线程,但耗时很短。并发标记阶段是从GC Roots开始对堆中对象进行可达性分析,找出存活对象,这一阶段耗时较长但能与用户线程并发运行。而最终标记阶段需要吧Remembered Set Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但可并行执行。最后筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划,这一过程同样是需要停顿线程的,但Sun公司透露这个阶段其实也可以做到并发,但考虑到停顿线程将大幅度提高收集效率,所以选择停顿。

特点

  • 并行与并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
  • 分代收集:分代概念在G1中依然得以保留。虽然G1可以不需要其它收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。也就是说G1可以自己管理新生代和老年代了。
  • 空间整合:由于G1使用了独立区域(Region)概念,G1从整体来看是基于“标记整理”算法实现收集,从局部(两个Region)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片。
  • 可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用这明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。

转载请注明:迷路的老鼠 » 垃圾回收器

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

网友最新评论 (2)

  1. 不同的情况有不同的「最好参数」,没有最好的,只有最适合自己的。
    wa5年前 (2019-10-10)回复
    • 确实是没有最好的标准
      迷路的老鼠5年前 (2019-10-11)回复