之前已经讲过了不少有关 GC 的内容,今天准备将之前没有细讲的部分进行补充,首先要提到的就是垃圾收集器。

基础的回收方式有三种:清除压缩复制,衍生出来的垃圾收集器有:

Serial 收集器

新生代收集器,使用停止复制算法,使用一个线程进行 GC ,串行,其它工作线程暂停。

使用-XX:+UseSerialGC开关来控制使用Serial + Serial Old模式运行进行内存回收(这也是虚拟机在 Client 模式下运行的默认值)。

ParNew 收集器

新生代收集器,使用停止复制算法,Serial 收集器的多线程版,用多个线程进行 GC ,并行,其它工作线程暂停,关注缩短垃圾收集时间。

使用-XX:+UseParNewGC开关来控制使用ParNew + Serial Old收集器组合收集内存;使用-XX:ParallelGCThreads来设置执行内存回收的线程数。

Parallel Scavenge 收集器

新生代收集器,使用停止复制算法,关注 CPU 吞吐量,即运行用户代码的时间/总时间,比如:JVM 运行 100 分钟,其中运行用户代码 99 分钟,垃 圾收集 1 分钟,则吞吐量是 99% ,这种收集器能最高效率的利用 CPU ,适合运行后台运算(其他关注缩短垃圾收集时间的收集器,如 CMS ,等待时间很少,所以适 合用户交互,提高用户体验)。

使用-XX:+UseParallelGC开关控制使用Parallel Scavenge + Serial Old收集器组合回收垃圾(这也是在 Server 模式下的默认值);使用-XX:GCTimeRatio来设置用户执行时间占总时间的比例,默认 99 ,即 1% 的时间用来进行垃圾回收。使用-XX:MaxGCPauseMillis设置 GC 的最大停顿时间(这个参数只对 Parallel Scavenge 有效),用开关参数-XX:+UseAdaptiveSizePolicy可以进行动态控制,如自动调整 Eden / Survivor 比例,老年代对象年龄,新生代大小等,这个参数在 ParNew 下没有。

Serial Old 收集器

老年代收集器,单线程收集器,串行,使用标记-整理算法,使用单线程进行GC,其它工作线程暂停(注意:在老年代中进行标记-整理算法清理,也需要暂停其它线程),在JDK1.5之前,Serial Old 收集器与 ParallelScavenge 搭配使用。

整理的方法是 Sweep (清除)和 Compact (压缩),清除是将废弃的对象干掉,只留幸存的对象,压缩是移动对象,将空间填满保证内存分为2块,一块全是对象,一块空闲),

Parallel Old 收集器

老年代收集器,多线程,并行,多线程机制与 Parallel Scavenge 差不错,使用标记-整理算法,在 Parallel Old 执行时,仍然需要暂停其它工作线程。

Parallel Old 收集器的整理,与 Serial Old 不同,这里的整理是Copy(复制)和Compact(压缩),复制的意思就是将幸存的对象复制到预先准备好的区域,而不是像Sweep(清除)那样清除废弃的对象。

Parallel Old 在多核计算中很有用。 Parallel Old 出现后(JDK 1.6),与 Parallel Scavenge 配合有很好的效果,充分体现 Parallel Scavenge 收集器吞吐量优先的效果。使用-XX:+UseParallelOldGC开关控制使用Parallel Scavenge + Parallel Old组合收集器进行收集。

CMS

全称 Concurrent Mark Sweep,老年代收集器,致力于获取最短回收停顿时间(即缩短垃圾回收的时间),使用标记-清除算法,多线程,优点是并发收集(用户线程可以和 GC 线程同时工作),停顿小。

使用-XX:+UseConcMarkSweepGC进行ParNew + CMS + Serial Old进行内存回收,优先使用ParNew + CMS(原因见后面),当用户线程内存不足时,采用备用方案Serial Old收集。

如何开始

首先来看一下 CMS 是在什么情况下进行 GC:

  1. 首先 JVM 根据-XX:CMSInitiatingOccupancyFraction-XX:+UseCMSInitiatingOccupancyOnly来决定什么时间开始垃圾收集。
  2. 如果设置了-XX:+UseCMSInitiatingOccupancyOnly,那么只有当老年代占用确实达到了-XX:CMSInitiatingOccupancyFraction参数所设定的比例时才会触发 CMS GC。
  3. 如果没有设置-XX:+UseCMSInitiatingOccupancyOnly,那么系统会根据统计数据自行决定什么时候触发 CMS GC。因此有时会遇到设置了 80% 比例才 CMS GC,但是 50% 时就已经触发了,就是因为这个参数没有设置的原因。

具体执行

CMS GC 的执行过程,具体来说就是:

初始标记(CMS-initial-mark)

该阶段是 stop the world 阶段,因此此阶段标记的对象只是从 root 集最直接可达的对象。

此阶段会打印 1 条日志:CMS-initial-mark:961330K(1572864K),指标记时,老年代的已用空间和总空间

并发标记(CMS-concurrent-mark)

此阶段是和应用线程并发执行的,所谓并发收集器指的就是这个,主要作用是标记可达的对象,此阶段不需要用户线程停顿。

此阶段会打印 2 条日志:CMS-concurrent-mark-start,CMS-concurrent-mark

预清理(CMS-concurrent-preclean)

此阶段主要是进行一些预清理,因为标记和应用线程是并发执行的,因此会有些对象的状态在标记后会改变,此阶段正是解决这个问题。因为之后的 CMS-remark 阶段也会 stop the world,为了使暂停的时间尽可能的小,也需要 preclean 阶段先做一部分工作以节省时间。

此阶段会打印 2 条日志:CMS-concurrent-preclean-start,CMS-concurrent-preclean

可控预清理(CMS-concurrent-abortable-preclean)

此阶段的目的是使 CMS GC 更加可控一些,作用也是执行一些预清理,以减少 CMS-remark 阶段造成应用暂停的时间。

此阶段涉及几个参数:

    -XX:CMSMaxAbortablePrecleanTime:当 abortable-preclean 阶段执行达到这个时间时才会结束。
-XX:CMSScheduleRemarkEdenSizeThreshold(默认2m):控制 abortable-preclean 阶段什么时候开始执行,即当年轻代使用达到此值时,才会开始 abortable-preclean 阶段。
-XX:CMSScheduleRemarkEdenPenetratio(默认50%):控制 abortable-preclean 阶段什么时候结束执行。

此阶段会打印 3 条日志:CMS-concurrent-abortable-preclean-start,CMS-concurrent-abortable-preclean,CMS:abort preclean due to time XXX

重新标记(CMS-remark)

此阶段暂停应用线程,停顿时间比并发标记小得多,但比初始标记稍长,因为会对所有对象进行重新扫描并标记。

此阶段会打印以下日志:

  1. YG occupancy:964861K(2403008K),指执行时年轻代的情况。
  2. CMS remark:961330K(1572864K),指执行时老年代的情况。
  3. 此外,还打印出了弱引用处理、类卸载等过程的耗时。

并发清除(CMS-concurrent-sweep)

此阶段进行并发的垃圾清理。

并发重设状态等待下次CMS的触发(CMS-concurrent-reset)

此阶段是为下一次 CMS GC 重置相关数据结构。

总结

CMS 的收集过程,概括一下就是:2 次标记,2 次预清除,1 次重新标记,1 次清除。

在CMS清理过程中,只有初始标记和重新标记需要短暂停顿用户线程,并发标记和并发清除都不需要暂停用户线程,因此效率很高,很适合高交互的场合。

CMS也有缺点,它需要消耗额外的 CPU 和内存资源。在 CPU 和内存资源紧张,会加重系统负担(CMS 默认启动线程数为( CPU数量 + 3 ) / 4 )。

另外,在并发收集过程中,用户线程仍然在运行,仍然产生内存垃圾,所以可能产生“浮动垃圾”(本次无法清理,只能下一次Full GC才清理)。因此在 GC 期间,需要预留足够的内存给用户线程使用。

所以使用 CMS 的收集器并不是老年代满了才触发 Full GC ,而是在使用了一大半(默认 68% ,即 2/3 ,使用-XX:CMSInitiatingOccupancyFraction来设置)的时候就要进行 Full GC。如果用户线程消耗内存不是特别大,可以适当调高-XX:CMSInitiatingOccupancyFraction以降低 GC 次数,提高性能。如果预留的用户线程内存不够,则会触发 Concurrent Mode Failure,此时,将触发备用方案:使用 Serial Old 收集器进行收集,但这样停顿时间就长了,因此-XX:CMSInitiatingOccupancyFraction不宜设的过大。

还有,CMS 采用的是标记-清除算法,会导致内存碎片的产生,可以使用-XX:+UseCMSCompactAtFullCollection来设置是否在 Full GC 之后进行碎片整理,用-XX:CMSFullGCsBeforeCompaction来设置在执行多少次不压缩的 Full GC 之后,来一次带压缩的 Full GC。

并发和并行

并发收集:

指用户线程与GC线程同时执行(不一定是并行,可能交替,但总体上是在同时执行的),不需要停顿用户线程(其实在 CMS 中用户线程还是需要停顿的,只是非常短,GC 线程在另一个 CPU 上执行);

并行收集:

指多个 GC 线程并行工作,但此时用户线程是暂停的;

所以,Serial 是串行的,Parallel 收集器是并行的,而 CMS 收集器是并发的。

总结

今天了解了一下普通的垃圾收集器,并且详细介绍了 CMS,其特性其实是基于普通的垃圾算法,增加了预处理、预清除的过程,因此效率更加优越。当然它也有自己的缺点,更加消耗资源,因此在选用的时候需要结合实际场景。

有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。

https://death00.github.io/

GC 知识点补充——CMS的更多相关文章

  1. swagger知识点补充

    1. swagger知识点补充 1.1. 概述 在swagger的使用过程中,除了网上常见的例子,还会有很多细节上的东西需要注意和改写,这里我列几点我使用过程中遇到的问题和改进方式 1.2. 知识点 ...

  2. set集合,深浅拷⻉以及部分知识点补充

    set集合,深浅拷⻉以及部分知识点补充内容:1. 基础数据类型补充2. set集合3. 深浅拷⻉主要内容: ⼀. 基础数据类型补充⾸先关于int和str在之前的学习中已经讲了80%以上了. 所以剩下的 ...

  3. Django 知识点补充

    Django 知识点补充 1 Django如何在Model保存前做一定的固定操作,比如写一条日志 (1)利用Django的Model的Signal Dispatcher, 通过django.db.mo ...

  4. 从零开始的全栈工程师——html篇1.8(知识点补充与浏览器兼容性)

    知识点补充 一.浏览器的兼容问题(关于浏览器的兼容问题 有很多大佬已经解释的很清楚了 这个得自己百度去多花点时间去了解 这里咱们只说一下前面的漏点) 浏览器兼容性问题又被称为网页兼容性或网站兼容性问题 ...

  5. python day4 元组/字典/集合类知识点补充

    目录 python day4 元组/字典/集合类知识点补充 1. 元组tuple知识点补充 2. 字典dict的知识点补充 3. 基本数据类型set 4. 三元运算,又叫三目运算 5. 深复制浅复制 ...

  6. python 知识点补充

    python 知识点补充 简明 python 教程 r 或 R 来指定一个 原始(Raw) 字符串 Python 是强(Strongly)面向对象的,因为所有的一切都是对象, 包括数字.字符串与 函数 ...

  7. java基础知识点补充---二维数组

    #java基础知识点补充---二维数组 首先定义一个二维数组 int[][] ns={ {1,2,3,4}, {5,6,7,8}, {9,10,11,12}, {13,14,15,16} }; 实现遍 ...

  8. disruptor笔记之八:知识点补充(终篇)

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  9. Jaeger知识点补充

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

随机推荐

  1. 在Debian上用FVWM做自己的桌面

    用FVWM做自己的桌面 Table of Contents 1. 前言 2. 学习步骤 3. 准备 3.1. 软件包 3.2. 字体 3.3. 图片 3.4. 参考资料 4. 环境 5. 布局 6. ...

  2. html标签和css基础语法与浏览器兼容性等相关基础学习

    <!-- table的使用 --> <h3>前端日常</h3> <form action="https://www.baidu.com"& ...

  3. 学习WEBAPI第一天

    目录 WebApi: 通过操作对象来实现操作标签的目的 一.DOM 中常用的操作 二.doucument对象 三.获取元素 四.注册事件 五.操作元素的属性 六.当页面加载完时,script代码已经执 ...

  4. Ubuntu 搭建Zookeeper服务

    1.下载安装包 官方下载地址http://apache.fayea.com/zookeeper/ 2.安装 安装前确保系统已安装过JDK,JDK安装过程可参照 2.1 解压下载好的tar.gz安装包到 ...

  5. Linux 删除命令rm

    Linux rm命令用于删除一个文件或者目录. 语法:rm [options] name... 参数: 1.-i 删除前逐一询问确认. 2.-f 即使原档案属性设为唯读,亦直接删除,无需逐一确认. 3 ...

  6. Dart数据类型

    变量与常量: 变量: 使用var声明变量,可以赋予不同数据类型的值, 未初始化时默认值是null 使用final声明的变量只能被赋值一次 void main(){ var a; print(a); a ...

  7. Nginx负载均衡配置实例

    面对高并发的问题,企业往往会从两个方面来解决.其一,从硬件上面,提升硬件的配置,增加服务器的性能:另外,就是从软件上,将数据库和WEB服务器分离,使数据库和WEB服务器都能够充分发挥各自的性能,并且二 ...

  8. 如何安装PHPstorm并配置php运行环境运行php项

    php是程序开发者常用的编程语言,那么它就需要一款简单实用的集成开发环境,也就是一款舒服的编辑器,今天笔者为大家介绍phpstorm这款软件,并详细的讲解如何用它配置php项目   工具/原料   笔 ...

  9. [书籍翻译] 《JavaScript并发编程》第一章 JavaScript并发简介

    > 本文是我翻译<JavaScript Concurrency>书籍的第一章,该书主要以Promises.Generator.Web workers等技术来讲解JavaScript并 ...

  10. Android开发——实现子线程更新UI

    Android中线程按功能分的话,可以分为两个,一个是主线程(UI线程),其他的都是子线程 主线程不能执行那些耗时过长的代码或任务(执行耗时过长的代码会出现应用未响应的提示),所以都是使用子线程来执行 ...