JVM垃圾收集器

https://yq.aliyun.com/articles/655845?spm=a2c4e.11153940.blogrightarea272406.27.167a7ea1MJqw0B

博客参考了不少书籍和博客,有些图别人画的比较好,所以我就偷了个懒没有重新画,别介意哈。

 垃圾收集器

66b6cfc36bf80af53a61466301f93ccfd507ba6f

GC实现目标: 准确、高效、低停顿、空闲内存规整.

 新生代

1  Serial收集器

Serial收集器是Hotspot运行在Client模式下的默认新生代收集器, 它的特点是 只用一个CPU/一条收集线程去完成GC工作, 且在进行垃圾收集时必须暂停其他所有的工作线程(“Stop The World” -后面简称STW).

d8416630e366854e2b1141ec4189787ffa47b0a7

虽然是单线程收集, 但它却简单而高效, 在VM管理内存不大的情况下(收集几十M~一两百M的新生代), 停顿时间完全可以控制在几十毫秒~一百多毫秒内.

 

2  ParNew收集器

ParNew收集器其实是前面Serial的多线程版本, 除使用多条线程进行GC外, 包括Serial可用的所有控制参数、收集算法、STW、对象分配规则、回收策略等都与Serial完全一样(也是VM启用CMS收集器-XX: +UseConcMarkSweepGC的默认新生代收集器).

8085f0122e7ace059ca93d350327279953141bdc

由于存在线程切换的开销, ParNew在单CPU的环境中比不上Serial, 且在通过超线程技术实现的两个CPU的环境中也不能100%保证能超越Serial. 但随着可用的CPU数量的增加, 收集效率肯定也会大大增加(ParNew收集线程数与CPU的数量相同, 因此在CPU数量过大的环境中, 可用-XX:ParallelGCThreads参数控制GC线程数).

 

3  Parallel Scavenge收集器

与ParNew类似, Parallel Scavenge也是使用复制算法, 也是并行多线程收集器. 但与其他收集器关注尽可能缩短垃圾收集时间不同, Parallel Scavenge更关注系统吞吐量:

系统吞吐量=运行用户代码时间(运行用户代码时间+垃圾收集时间)

停顿时间越短就越适用于用户交互的程序-良好的响应速度能提升用户的体验;而高吞吐量则适用于后台运算而不需要太多交互的任务-可以最高效率地利用CPU时间,尽快地完成程序的运算任务. Parallel Scavenge提供了如下参数设置系统吞吐量:

Parallel Scavenge参数

描述

MaxGCPauseMillis

(毫秒数) 收集器将尽力保证内存回收花费的时间不超过设定值, 但如果太小将会导致GC的频率增加.

GCTimeRatio

(整数:0 < GCTimeRatio < 100) 是垃圾收集时间占总时间的比率

-XX:+UseAdaptiveSizePolicy

启用GC自适应的调节策略: 不再需要手工指定-Xmn、-XX:SurvivorRatio、-XX:PretenureSizeThreshold等细节参数, VM会根据当前系统的运行情况收集性能监控信息, 动态调整这些参数以提供最合适的停顿时间或最大的吞吐量

 

 老年代

1  Serial Old收集器

Serial Old是Serial收集器的老年代版本, 同样是单线程收集器,使用“标记-整理”算法:

7b20b43a1450b7478e88d2a3fc7463ab4574a33c

Serial Old应用场景如下:

Ø   JDK 1.5之前与Parallel Scavenge收集器搭配使用;

Ø   作为CMS收集器的后备预案, 在并发收集发生Concurrent Mode Failure时启用(见下:CMS收集器).

2  Parallel Old收集器

Parallel Old是Parallel Scavenge收老年代版本, 使用多线程和“标记-整理”算法, 吞吐量优先, 主要与Parallel Scavenge配合在 注重吞吐量 及 CPU资源敏感 系统内使用:

9701a0c6562b465be6fd7463d7332fd03ba7ea4f

 

3  CMS收集器

CMS(Concurrent Mark Sweep)收集器是一款具有划时代意义的收集器, 一款真正意义上的并发收集器, 虽然现在已经有了理论意义上表现更好的G1收集器, 但现在主流互联网企业线上选用的仍是CMS(如Taobao、微店).

CMS是一种以获取最短回收停顿时间为目标的收集器(CMS又称多并发低暂停的收集器), 基于”标记-清除”算法实现, 整个GC过程分为以下4个步骤:

1. 初始标记(CMS initial mark)

2. 并发标记(CMS concurrent mark: GC Roots Tracing过程)

3. 重新标记(CMS remark)

4. 并发清除(CMS concurrent sweep: 已死象将会就地释放, 注意: 此处没有压缩)

其中两个加粗的步骤(初始标记、重新标记)仍需STW. 但初始标记仅只标记一下GC Roots能直接关联到的对象, 速度很快; 而重新标记则是为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录, 虽然一般比初始标记阶段稍长, 但要远小于并发标记时间.

f7d1090677982a7eb179be4be08865a38eef8eb8

(由于整个GC过程耗时最长的并发标记和并发清除阶段的GC线程可与用户线程一起工作, 所以总体上CMS的GC过程是与用户线程一起并发地执行的.

由于CMS收集器将整个GC过程进行了更细粒度的划分, 因此可以实现并发收集、低停顿的优势, 但它也并非十分完美, 其存在缺点及解决策略如下:

Ø   CMS默认启动的回收线程数=(CPU数目+3)4

当CPU数>4时, GC线程最多占用不超过25%的CPU资源, 但是当CPU数<=4时, GC线程可能就会过多的占用用户CPU资源, 从而导致应用程序变慢, 总吞吐量降低.

Ø   无法处理浮动垃圾, 可能出现Promotion Failure、Concurrent Mode Failure而导致另一次Full GC的产生。

浮动垃圾是指在CMS并发清理阶段用户线程运行而产生的新垃圾. 由于在GC阶段用户线程还需运行, 因此还需要预留足够的内存空间给用户线程使用, 导致CMS不能像其他收集器那样等到老年代几乎填满了再进行收集. 因此CMS提供了-XX:CMSInitiatingOccupancyFraction参数来设置GC的触发百分比(以及-XX:+UseCMSInitiatingOccupancyOnly来启用该触发百分比), 当老年代的使用空间超过该比例后CMS就会被触发(JDK 1.6之后默认92%). 但当CMS运行期间预留的内存无法满足程序需要, 就会出现上述Promotion Failure等失败, 这时VM将启动后备预案: 临时启用Serial Old收集器来重新执行Full GC(CMS通常配合大内存使用, 一旦大内存转入串行的Serial GC, 那停顿的时间就是大家都不愿看到的了).

Ø   由于CMS采用”标记-清除”算法实现, 可能会产生大量内存碎片.

内存碎片过多可能会导致无法分配大对象而提前触发Full GC. 因此CMS提供了-XX:+UseCMSCompactAtFullCollection开关参数, 用于在Full GC后再执行一个碎片整理过程. 但内存整理是无法并发的, 内存碎片问题虽然没有了, 但停顿时间也因此变长了, 因此CMS还提供了另外一个参数-XX:CMSFullGCsBeforeCompaction用于设置在执行N次不进行内存整理的Full GC后, 跟着来一次带整理的(默认为0: 每次进入Full GC时都进行碎片整理).

 

 

 分区收集- G1收集器

1  G1简介

G1(Garbage-First)是一款面向服务端应用的收集器, 主要目标用于配备多颗CPU的服务器治理大内存.

- G1 is planned as the long term replacement for the Concurrent Mark-Sweep Collector (CMS).

- -XX:+UseG1GC 启用G1收集器.

与其他基于分代的收集器不同, G1将整个Java堆划分为多个大小相等的独立区域(Region), 虽然还保留有新生代和老年代的概念, 但新生代和老年代不再是物理隔离的了, 它们都是一部分Region(不需要连续)的集合.

82772560c9c480a979dcb7f03e61dd517d4bf8cc

每块区域既有可能属于O区、也有可能是Y区, 因此不需要一次就对整个老年代/新生代回收. 而是当线程并发寻找可回收的对象时, 有些区块包含可回收的对象要比其他区块多很多. 虽然在清理这些区块时G1仍然需要暂停应用线程, 但可以用相对较少的时间优先回收垃圾较多的Region(这也是G1命名的来源). 这种方式保证了G1可以在有限的时间内获取尽可能高的收集效率.

 

2  新生代收集

04c99fa2fb602a5a616e1de21fb96fe2118bf460

G1的新生代收集跟ParNew类似: 存活的对象被转移到一个/多个Survivor Regions. 如果存活时间达到阀值, 这部分对象就会被提升到老年代.

796b73e5f888d262a403994f01b338d811a332f5

G1的新生代收集特点如下:

Ø   一整块堆内存被分为多个Regions.

Ø   存活对象被拷贝到新的Survivor区或老年代.

Ø   年轻代内存由一组不连续的heap区组成, 这种方法使得可以动态调整各代区域尺寸.

Ø   Young GCs会有STW事件, 进行时所有应用程序线程都会被暂停.

Ø   多线程并发GC.

 

3  老年代收集

G1老年代GC会执行以下阶段:

注: 一下有些阶段也是年轻代垃圾收集的一部分.

index

Phase

Description

(1)

初始标记 (Initial Mark: Stop the World Event)

在G1中, 该操作附着一次年轻代GC, 以标记Survivor中有可能引用到老年代对象的Regions.

(2)

扫描根区域 (Root Region Scanning: 与应用程序并发执行)

扫描Survivor中能够引用到老年代的references. 但必须在Minor GC触发前执行完.

(3)

并发标记 (Concurrent Marking : 与应用程序并发执行)

在整个堆中查找存活对象, 但该阶段可能会被Minor GC中断.

(4)

重新标记 (Remark : Stop the World Event)

完成堆内存中存活对象的标记. 使用snapshot-at-the-beginning(SATB, 起始快照)算法, 比CMS所用算法要快得多(空Region直接被移除并回收, 并计算所有区域的活跃度).

(5)

清理 (Cleanup : Stop the World Event and Concurrent)

见下 5-1、2、3

 

5-1 (Stop the world)

在含有存活对象和完全空闲的区域上进行统计

 

5-2 (Stop the world)

擦除Remembered Sets.

 

5-3 (Concurrent)

重置空regions并将他们返还给空闲列表(free list)

(*)

Copying/Cleanup (Stop the World Event)

选择”活跃度”最低的区域(这些区域可以最快的完成回收). 拷贝/转移存活的对象到新的尚未使用的regions. 该阶段会被记录在gc-log内(只发生年轻代[GC pause (young)], 与老年代一起执行则被记录为[GC Pause (mixed)].

详细步骤可参考 Oracle官方文档-The G1 Garbage Collector Step by Step.

4  G1老年代GC特点

1)   并发标记阶段(index 3)

Ø   在与应用程序并发执行的过程中会计算活跃度信息.

Ø   这些活跃度信息标识出那些regions最适合在STW期间回收(which regions will be best to reclaim during an evacuation pause).

Ø   不像CMS有清理阶段.

2)   再次标记阶段(index 4)

Ø   使用Snapshot-at-the-Beginning(SATB)算法比CMS快得多.

Ø   空region直接被回收.

3)   拷贝/清理阶段(Copying/Cleanup Phase)

Ø   年轻代与老年代同时回收.

Ø   老年代内存回收会基于他的活跃度信息.

 

5  Remembered Set

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

 

posted @ 2018-10-26 08:28  雨下个不停  阅读(162)  评论(0编辑  收藏  举报