BiBi - JVM -5- 垃圾回收器

From:深入理解Java虚拟机

  • 目录
    BiBi - JVM -0- 开篇
    BiBi - JVM -1- Java内存区域
    BiBi - JVM -2- 对象
    BiBi - JVM -3- 垃圾收集算法
    BiBi - JVM -4- HotSpot JVM
    BiBi - JVM -5- 垃圾回收器
    BiBi - JVM -6- 回收策略
    BiBi - JVM -7- Java类文件结构
    BiBi - JVM -8- 类加载机制
    BiBi - JVM -9- 类加载器
    BiBi - JVM -10- 虚拟机字节码
    BiBi - JVM -11- 编译期优化
    BiBi - JVM -12- 运行期优化
    BiBi - JVM -13- 并发

新生代:Serial、ParNew、Parallel Scavenge、G1
老年代:Serial Old、CMS、Parallel Old、G1

并行【Parallel】:指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
并发【Concurrent】:指用户线程与垃圾收集线程同时执行【但不一定是并行的,可能会交替执行】,用户程序在继续运行。而垃圾收集程序运行在另一个CPU上。

BiBi - JVM -5- 垃圾回收器_第1张图片

1. Serial收集器

单线程收集器,在进行垃圾收集时,必须暂停其他所有的工作线程,直到收集结束。是虚拟机运行在Client模式下的默认【新生代】收集器。与其他收集器相比的优势:没有线程交互的开销,专心做垃圾收集,简单高效。

2. ParNew收集器

是Serial收集器的多线程版本。是虚拟机运行在Service模式下首选【新生代】收集器。一个原因是:除了Serial收集器外,只有ParNew能与CMS收集器配合使用。即新生代多线程收集器能与CMS收集器的就ParNew这么一个。当CPU数量增多时【CPU > 2】,它的性能才会超越Serial收集器。

3. Parallel Scavenge收集器【吞吐量优先收集器】

是新生代并行的多线程收集器。其它收集器的关注点都是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标是达到一个可控制的吞吐量

吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)
停顿时间越短就越适合需要与用户交互的程序,提高响应速度,改善用户体验;
高吞吐量可以高效利用CPU时间,适合在后台运算而【不需要太多交互的任务】。

与ParNew收集器一个重要区别:Parallel Scavenge具有【GC自适应的调节策略】,即虚拟机能根据当前系统的运行情况收集性能监控信息,动态调整新生代中Eden与Survivor的比例,以提供最适合的停顿时间或最大的吞吐量。

注:Parallel Scavenge收集器和G1收集器没有使用传统的GC收集器代码框架,可能基于这个原因他们不能与CMS收集器配合使用。

4. Serial Old收集器

单线程老年代收集器,主要在Client模式下使用。

5. Parallel Old收集器

多线程老年代收集器,适用于:注重吞吐量以及CPU资源敏感的场合

6. CMS收集器【并发收集,低停顿】

BiBi - JVM -5- 垃圾回收器_第2张图片

CMS【Concurrent Mark Sweep,并发标记清除】采用【标记 - 清除】算法的老年代收集器,而Serial Old和Parallel Old采用的是【标记 - 整理】算法。

  • CMS收集器工作的步骤

1)初始标记 【需要stop the world】
仅仅只是标记GC Roots直接关联到的对象,速度很快。
2)并发标记
进行GC Roots Tracing过程。
3)重新标记【需要stop the world】
修正并发标记期间因用户程序继续运作而导致标记发生变动的那一个部分对象的标记记录,这个阶段停顿的时间会比初始标记阶段稍长。
4)并发清除
整个过程中耗时最长的并发标记和并发清除过程,但是这两个过程都可以与用户线程一起工作。所以,从整体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。

  • CMS收集器的问题

1)并发阶段,会因占用一部分线程而导致应用程序变慢,总吞吐量降低。当CPU负载较大时,还要分出一半的运算能力去执行收集器线程,可能导致用户程序的执行速度忽然降低50%,这是难以接受的。

解决:采用【增量式并发收集器】的CMS收集器变种,所做的事情和单CPU年代PC机操作系统使用【抢占式模拟多任务机制】的思想一样,在并发标记和清理的时候,让GC线程、用户线程交替运行,尽量减少GC线程独占资源的时间。这样这个垃圾收集的过程会更长,但对用户程序的影响会减少。【效果一般,现在已经被弃用】

2)由于CMS并发清理垃圾时,用户线程还在运行,也会产生新的垃圾,这一部分垃圾CMS无法当次处理,只能等待下一次GC时再清理,这部分垃圾称为【浮动垃圾】。所以CMS不能像其他收集器那样,等到老年代几乎被填满时再进行收集,JDK1.5阈值为68%,JDK1.6阈值为92%【老年代使用了92%的内存后触发GC】。

所以,当CMS运行期间预留的内存无法满足浮动垃圾占用的内存,就会出现一次 Concurrent Mode Failure 失败,这时会启动预备方案:使用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿的时间就很长了,导致性能降低。

3)CMS采用【标记-清除】算法,会产出大量碎片空间。解决:在CMS进行FullGC时进行内存碎片合并整理过程,该过程无法并发。或者设置执行多少次不压缩的FullGC后,进行一次带压缩的。

7. G1【Garbage-First】收集器

是一款面向服务端应用的垃圾收集器。不需要其他收集器配合就能独立管理整个GC堆,它能够采用不同的处理方式去处理新创建的对象、已存活一段时间、熬过多次GC的旧对象以获取更好的收集效果。

G1从整体看是基于【标记-整理】算法,从局部看是基于【复制】算法,这意味着G1不会产生内存空间碎片。

  • 可预测的停顿

这是比CMS的另一大优势,G1能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。【依据Region:它可以有计划地避免在整个Java堆中进行全局的垃圾回收】

  • Region

其它收集器进行收集的范围是整个新生代或者老年代,而G1不是,它将整个Java堆划分为多个大小相等的独立区域【Region】,虽然还保留新生代和老年代的概念,但是新生代和老年代不再是物理隔离的了,它们都是一部分Region【不需要连续】的集合。

  • 回收思路

G1跟踪各个Region里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region【这也是Garbage-First的由来】。其中,垃圾堆的价值大小体现在:回收所获得的空间大小和回收所需时间。
Region的划分和有优先级的回收,保证G1在有限时间内获取尽可能高的收集效率。

  • 问题

问题:Region不可能是孤立的,一个对象并不只能被本Region中的其它对象引用,而是整个Java堆中的任意对象都可以引用它。在做可达性分析对象是否存活时,要扫描整个Java堆吗?跟其它收集器也有一样的问题,回收新生代时,要扫描整个老年代吗?

解决:在G1收集器中,Region之间的对象引用以及其他收集器中的新生代和老年代之间的对象引用,虚拟机都是使用Remembered Set来避免全堆扫描的。G1中每个Region都有一个与之对应的Remember Set,虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个Writer Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中【在分代的例子中就是检查是否老年代中的对象引用了新生代中的对象】,如果是便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remember Set之中。当进行内存回收时,在GC根节点的枚举范围中加入Remember Set即可保证不对全堆扫描也不会有遗漏。

  • G1收集过程【与CMS类似】
BiBi - JVM -5- 垃圾回收器_第3张图片

1)初始标记
2)并发标记
3)最终标记
为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remember Set Logs里面,最终标记阶段需要把Remember Set Logs的数据合并到Remember Set中,这个阶段需要停顿其它线程,但可并行执行。
4)筛选回收
首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。这个阶段也是可以做到和用户线程一起并发执行,

追求低停顿,可以尝试G1;追求吞吐量,暂时不建议尝试G1。

你可能感兴趣的:(BiBi - JVM -5- 垃圾回收器)