Loading

Java虚拟机——垃圾收集器

1 七种垃圾收集器

  1. Serial(串行GC)—— 标记-复制
  2. ParNew(并行GC)—— 标记-复制
  3. Parallel Scavenge(并行回收GC)—— 标记-复制
  4. Serial Old(MSC)(串行GC)—— 标记-整理
  5. CMS(并发GC)—— 标记-清除
  6. Parallel Old(并行GC)—— 标记-整理
  7. G1(JDK1.7update14才可以正式商用)

说明:

  1. 1~3用于新生代代垃圾回收:新生代的垃圾回收称为Minor GC/ Young GC
  2. 4~6用于老年代垃圾回收(当然也可以用于方法区的回收):老年代的垃圾回收称为Major GC / Old GC
  3. G1使用混合收集 Mixed GC,即收集的目标是整个新生代和部分老年代
  4. 整堆收集 (Full GC):收集整个Java堆和方法区的垃圾。

注意:并行与并发

  1. 并行:多条GC线程同时操作
  2. 并发:GC线程与用户线程一起操作

2 常用五种组合

  1. Serial/Serial Old
  2. ParNew/Serial Old:与上边相比,只是比年轻代多了多线程垃圾回收而已
  3. ParNew/CMS:当下比较高效的组合
  4. Parallel Scavenge/Parallel Old:自动管理的组合
  5. G1:最先进的收集器,但是需要JDK1.7update14以上

3 串行收集器

在JDK1.3.1之前,单线程回收器是唯一的选择。它的单线程意义不仅仅是说它只会使用一个CPU或一个收集线程去完成垃圾收集工作。而且它进行垃圾回收的时候,必须暂停其他所有的工作线程(Stop The World,STW),直到它收集完成。适合Client模式的应用,在单CPU环境下,它简单高效,由于没有线程交互的开销,专心垃圾收集自然可以获得最高的单线程效率。

串行的垃圾收集器有两种,Serial与Serial Old,一般两者搭配使用。新生代采用Serial,是利用复制算法;老年代使用Serial Old采用标记-整理算法。Client应用或者命令行程序可以,通过-XX:+UseSerialGC可以开启上述回收模式。

image-20200828095122154

4 并行收集器

整体来说,并行垃圾回收相对于串行,是通过多个GC线程并行运行回收垃圾的。也会stop-the-world。适合Server模式以及多CPU环境。一般会和jdk1.5之后出现的CMS搭配使用。并行的垃圾回收器有以下几种:

ParNew:Serial收集器的多线程版本,默认开启的收集线程数和cpu数量一样,运行数量可以通过修改ParallelGCThreads设定。用于新生代收集,复制算法。使用-XX:+UseParNewGC,和Serial Old收集器组合进行内存回收。

image-20200828095020970

Parallel Scavenge: 关注吞吐量,吞吐量优先,也就是高效率利用cpu时间,尽快完成程序的运算任务可以设置最大停顿时间MaxGCPauseMillis以及,吞吐量大小GCTimeRatio。如果设置了-XX:+UseAdaptiveSizePolicy参数,则随着GC,会动态调整新生代的大小,Eden,Survivor比例等,以提供最合适的停顿时间或者最大的吞吐量。用于新生代收集,复制算法。通过-XX:+UseParallelGC参数,Server模式下默认提供了其和SerialOld进行搭配的分代收集方式。
$$
吞吐量=\frac{代码运行时间}{代码运行时间+垃圾收集时间}
$$
Parllel Old:Parallel Scavenge的老年代版本。JDK 1.6开始提供的。在此之前Parallel Scavenge的地位也很尴尬,没有并发老年代收集器与其配合使用。而有了Parllel Old之后,通过-XX:+UseParallelOldGC参数使用Parallel Scavenge + Parallel Old器组合进行内存回收,如下图所示。

image-20200828094859383

5 CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获得最短回收停顿时间为目标的收集器。从名字就能直到其是给予标记-清除算法的。但是它比一般的标记-清除算法要复杂一些,分为以下4个阶段:

初始标记:标记一下GC Roots能直接关联到的对象,会“Stop The World”,但是速度很快。

并发标记:从GC Roots直接关联对象开始遍历整个对象图,该过程耗时但是可以和用户线程并发执行。

重新标记:对标记期间产生的对象存活的再次判断,修正对这些对象的标记,执行时间相对并发标记短,比初始标记稍长,会“Stop The World”。

并发清除:清除标记阶段经判断已死亡的对象,可以和用户线程并发执行。

image-20200828132215199

由于垃圾回收线程可以和用户线程同时运行,也就是说它是并发的,那么它会对CPU的资源非常敏感,CMS默认启动的回收线程数是(CPU数量+3)/ 4,当CPU<4个时,并发回收是垃圾收集线程就不会少于25%,而且随着CPU减少而增加,这样会影响用户线程的执行。而且由于它是基于标记-清除算法的,那么就无法避免空间碎片的产生。CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。

​ 所谓浮动垃圾,在CMS并发清理阶段用户线程还在运行着,伴随程序运行自然还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只能留待下一次GC时再清理掉。

6 G1(Garbage First)收集器

在JDK 1.7确立是项目目标,在JDK 7u2版本之后发布,并在JDK 9中成为了默认的垃圾回收器。通过“-XX:+UseG1GC”启动参数即可指定使用G1 GC。

G1的收集模式:G1收集器对堆内存的划分与其他收集器完全不同。它可以面向堆内存任何部分来组成回收集(Collection Set简称为CSet)进行回收,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式。

G1对对内存的划分:G1不再坚持对堆内存以固定大小以及固定数量的分代区域进行划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region,每个Region都可以根据需要,扮演新生代的Eden空间,Survivor空间或者老年大空间。Region还有一类特殊的Humongous区域,专门用来存储大对象(只要大小超过Region容量的50%),Humongous Region会被G1视为老年代。对于超过了整个Region容量的超级大对象,则将会被放置在N个连续的Humongous Region之中。

Region的大小调整: 每个Region大小可以通过参数 -XX:G1HeapRegionSize设定,取值范围为1MB-32MB,且应为2的N次幂。

G1收集器Region分区示意图👇

image-20200828125310777

G1收集器的回收策略:G1收集器将Region作为单次回收的最小单元(每次收集的内存空间都是Region大小的整数倍),从而建立可预测的停顿时间模型 。具体的处理思路是:G1收集器跟踪各个Region里面的垃圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级表每次根据用户设置的收集停顿时间(使用参数-XX:MaxGCPauseMilis,默认200ms)优先先处理回收价值最大的Region。

G1从整体看还是基于标记-整理算法的,但是局部上是基于标记-复制算法的。这样就意味者它空间整合做的比较好,因为不会产生空间碎片。G1还是并发与并行的,它能够充分利用多CPU、多核的硬件环境来缩短“stop the world”的时间。


G1收集器的运作大致可以分为以下步骤:初始标记、并发标记、最终标记、筛选回收。

初始标记阶段:仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Set)的值,让下一个阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这个阶段需要停顿线程,但耗时很短。

PS:G1为每个Region设计了两个TAMS(Next Top at Mark Set)的指针,把Region中的一部分空间划分出来用于并发回收过程中对新对象内存分配,并发回收时新分配的对象地址都必须要在这两个指针位置以上。

并发标记阶段:从GC Roots开始对堆中对象进行可达性分析,找出要回收的对象,这阶段耗时较长,但是可以和用户线程并发运行。当对象图扫描完成后,需要后才能更新处理SATB(垃圾回收器开始时活着的对象的一个快照)记录下的在并发时有引用变动的对象。

最终标记阶段:为了修正在并发标记期间因用户程序继续运行而导致标记产生变化的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记需要把Remembered Set Logs的数据合并到Remembered Sets中,这阶段需要暂停线程,但是可并行执行。

筛选回收阶段:首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来确定一个由Region构成的回收集,然后把决定收回的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧的Region的全部空间。由于这些操作涉及到存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成。

image-20200828132640223

posted @ 2020-08-28 13:28  codeduck  阅读(188)  评论(0)    收藏  举报