JVM(五)垃圾回收器的前世此生

全文共 2195 个字,读完大约须要 8 分钟。算法

若是垃圾回收的算法属于内存回收的方法论的话,那本文讨论的垃圾回收器就属于内存回收的具体实现。数组

由于不一样的厂商(IBM、Oracle),实现的垃圾回收器各不相同,而本文要讨论的是 Oracle 的 HotSpot 虚拟机所使用的垃圾回收器。多线程

经常使用垃圾回收器,以下图所示:并发

经常使用垃圾回收器

  • 新生代回收器:Serial、ParNew、Parallel Scavenge
  • 老年代回收器:Serial Old、Parallel Old、CMS
  • 整堆回收器:G1

其中相互连线的垃圾回收器,表示能够相互搭配使用。性能

新生代 And 老生代

目前经常使用的商用垃圾收集器都使用的是分代垃圾回收方式。学习

分代垃圾回收器把内存分为:新生代(Young Generation)和老生代(Tenured Generation),以下图所示:线程

分代图

(图片来自fancydeepin)3d

默认状况下,新生代和老生代的内存比例是 1:2,该值能够经过 -XX:NewRatio 来设定。code

新生代(Young Generation)

程序中的大部分对象都符合“朝生夕死”的特性,因此绝大数新建立的对象都会存放在新生代,除非是大对象会直接进入老生代。新生代采用的是复制算法,这样能够更高效的回收内存空间。orm

新生代有细分为:Eden、Form Survivor、To Survivor 三个区域,默认的比例是 8:1:1,能够经过 -XX:SurvivorRatio 来设定。

新生代垃圾回收的执行过程:

一、Eden 区 + From Survivor 区存活着的对象复制到 To Survivor 区;

二、清空 Eden 和 From Survivor 分区;

三、From Survivor 和 To Survivor 分区交换(From 变 To,To 变 From)。

老生代(Tenured Generation)

老生代垃圾回收的频率比新生代低,存放的主要对象是:

一、新生代对象通过 N 次 GC 晋升到老年代。

能够经过设置 -XX:MaxTenuringThreshold=5 来设置,默认值是 15 次。

二、大对象直接存储到老生代。

所谓的“大对象”指的是须要连续存储空间的对象,好比:数组。

当大对象在新生代存储不下的时候,就须要分配担保机制,把当前新生代的全部对象复制到老年代中,由于分配担保机制须要涉及大量的复制,会致使性能问题,全部最好的方案是直接把大对象存储到老生代中。

经过参数 -xx:PretrnureSizeThreshold 来设定大对象的值。

注意:该参数只有 Serial 和 ParNew 垃圾回收器有效。

Serial

Serial 最先的垃圾回收器,JDK 1.3.1 以前新生代惟一的垃圾回收器,使用的是单线程串行回收方式,在单 CPU 环境下性能较好,由于单线程执行不存在线程切换。

线程类型: 单线程

使用算法: 复制算法

指定收集器: -XX:+UseSerialGC

Serial Old

Serial 收集器的老年代版本,一样也是单线程的。它有一个实用的用途做为CMS收集器的备选预案,后面介绍CMS的时候会详细介绍。

线程类型: 单线程

使用算法: 标记-整理

指定收集器: -XX:+UseSerialGC

ParNew

ParNew 其实就是 Serial 的多线程版本,能够和 Serial 共用不少控制参数,好比:-XX:SurvivorRatio , ParNew 能够和 CMS 配合使用。

parnew

(注:图片来源于零壹技术栈)

线程类型: 多线程

使用算法: 复制

指定收集器: -XX:+UseParNewGC

Parallel Scavenge

Parallel 和 ParNew 收集器相似,也是多线程的,但 Parallel 是吞吐量优先的收集器,GC停顿时间的缩短是以吞吐量为代价的,好比收集 100MB 的内存,须要 10S 的时间,CMS 则会缩短为 7S 收集 50 MB 的内存,这样停顿的时间确实缩少了,但收集的频率变大了,吞吐量就变小了。

线程类型: 多线程

使用算法: 复制

指定收集器: -XX:+UseParallelGC

Parallel Old

Parallel Old 是 Parallel 的老生代版本,一样是吞吐量优先的收集器。

线程类型: 多线程

使用算法: 标记-整理

指定收集器: -XX:+UseParallelOldGC

CMS

CMS(Concurrent Mark Sweep)一种以得到最短停顿时间为目标的收集器,很是适用B/S系统。

使用 Serial Old 整理内存。

CMS 运行过程:

CMS

(注:图片来源于零壹技术栈)

一、初始标记

标记 GC Roots 直接关联的对象,须要 Stop The World 。

二、并发标记

从 GC Roots 开始对堆进行可达性分析,找出活对象。

三、从新标记

从新标记阶段为了修正并发期间因为用户进行运做致使的标记变更的那一部分对象的标记记录。这个阶段的停顿时间通常会比初始标记阶段稍长一些,但远比并发标记的时间短,也须要 Stop The World 。

四、并发清除

除垃圾对象。

CMS 缺点:

一、对 CPU 资源要求敏感。

CMS 回收器过度依赖于多线程环境,默认状况下,开启的线程数为(CPU 的数量 + 3)/ 4,当 CPU 数量少于 4 个时,CMS 对用户自己的操做的影响将会很大,由于要分出一半的运算能力去执行回收器线程。

二、CMS没法清除浮动垃圾。

浮动垃圾指的是CMS清除垃圾的时候,还有用户线程产生新的垃圾,这部分未被标记的垃圾叫作“浮动垃圾”,只能在下次 GC 的时候进行清除。

三、CMS 垃圾回收会产生大量空间碎片。

CMS 使用的是标记-清除算法,全部在垃圾回收的时候回产生大量的空间碎片。

注意:CMS 收集器中,当老生代中的内存使用超过必定的比例时,系统将会进行垃圾回收;当剩余内存不能知足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时采用 Serial Old 算法进行清除,此时的性能将会下降。

线程类型: 多线程

使用算法: 标记-清除

指定收集器: -XX:+UseConcMarkSweepGC

G1

G1 GC 这是一种兼顾吞吐量和停顿时间的 GC 实现,是 JDK 9 之后的默认 GC 选项。G1 能够直观的设定停顿时间的目标,相比于 CMS GC,G1 未必能作到 CMS 在最好状况下的延时停顿,可是最差状况要好不少。

G1 GC 仍然存在着年代的概念,可是其内存结构并非简单的条带式划分,而是相似棋盘的一个个 region。Region 之间是复制算法,但总体上实际可看做是标记 - 整理(Mark-Compact)算法,能够有效地避免内存碎片,尤为是当 Java 堆很是大的时候,G1 的优点更加明显。

G1

G1 吞吐量和停顿表现都很是不错,而且仍然在不断地完善,与此同时 CMS 已经在 JDK 9 中被标记为废弃(deprecated),因此 G1 GC 值得深刻掌握。

G1 运行过程:

一、初始标记

标记 GC Roots 直接关联的对象,须要 Stop The World 。

二、并发标记

从 GC Roots 开始对堆进行可达性分析,找出活对象。

三、从新标记

从新标记阶段为了修正并发期间因为用户进行运做致使的标记变更的那一部分对象的标记记录。这个阶段的停顿时间通常会比初始标记阶段稍长一些,但远比并发标记的时间短,也须要 Stop The World 。

四、筛选回收

首先对各个 Region 的回收价值和成本进行排序,根据用户所指望的 GC 停顿时间来制定回收计划。这个阶段能够与用户程序一块儿并发执行,可是由于只回收一部分 Region,时间是用户可控制的。

线程类型: 多线程

使用算法: 复制、标记-整理

指定收集器: -XX:+UseG1GC(JDK 7u4 版本后可用)

参考

《深刻理解Java虚拟机》

《垃圾回收的算法与实现》

最后

关注公众号,发送“gc”关键字,领取《垃圾回收的算法与实现》学习资料。

相关文章
相关标签/搜索