垃圾处理器-CMS
一、简介
CMS垃圾收集器是一款用于老年代的,使用复制-清除-整理算法的垃圾收集器。
二、GC阶段
1、初始化标记(STW)
暂停应用程序线程,遍历 GC ROOTS 直接可达的对象并将其压入标记栈(mark-stack),标记完之后恢复应用程序线程。
2、并发标记
这个阶段虚拟机会分出若干线程(GC 线程)去进行并发标记。标记哪些对象呢?标记那些 GC ROOTS 最终可达的对象。具体做法是推出标记栈里面的对象,然后递归标记其直接引用的子对象,同样的把子对象压到标记栈中,重复推出,压入。。。直至清空标记栈。这个阶段GC线程和应用程序线程同时运行。
当 GC线程 进行并发操作时,应用程序可能会进行新增对象、删除对象、变更对象引用等一系列操作。这种条件下可能会出现活动对象的漏标的情况,比如下面场景:

- A 是活动对象,A->B,标记 B 可达,将其压入标记栈,此时 A 所有直接子对象遍历完,A 出栈,标记线程将不会再访问 A。
- 同时应用程序移掉了 B 对 C 的引用,让A重新引用 C。
- B 出栈时无法标记 C 可达,A 虽然引用 C 但标记线程不会再访问A,此时 C 会被当成不可达对象。
所以单纯的并发标记操作并不能保证 GC 的正确性,所以还需要额外的操作,这个操作就是 Write Barrier。
Write Barrier 就是当改写一个引用时:A.x = C,执行一些额外操作。如果是上面场景可以假设为:
write_barrier(obj, field, newobj){
if(newobj.mark == FALSE)
newobj.mark = TRUE
push(newobj, $mark_stack)
*field = newobj
}
即当赋值引用时,如果赋值的对象还没有被标记,将标记该对象将其压入标记栈。在使用 Write Bariier 之后同样的情景就不会出现活动对象被遗漏的情况了。

所以我们知道并发标记阶段,并不是只有单纯的并发标记,还有一个额外的 Write Bariier 操作,避免活动对象被漏标。
3、重新标记(STW)
重新标记可以理解成一个同步刷新对象间引用的操作,整个过程是 STW。在并发标记其间,应用程序不断变更对象引用,此时的 GC ROOTS 有可能会发生变化,这个时候需要同步更新这个增量变化。于是重新从当前的 GC ROOTS 和指针更新的区域出发(mod-union table)再进行一次标记,所以这个过程被叫作重新标记。需要注意的是:已经标记的对象是不会再遍历一次,标记线程识别对象在并发阶段已经标记过了,就会跳过该对象。所以重新标记只会遍历那些新增没有标记过的活动对象和其间有指针更新的活动对象,如果指针更新频繁,重新标记很有可能会遍历新生代中的大部分甚至全部对象。所以如果重新标记阶段很慢,可以启动一次YGC,来减少并发标记的工作量减少其停顿时间。
4、并发清除
重新标记结束后,应用程序继续运行,此时分出一个处理器去进行垃圾回收工作。
老年代的对象通常是存活时间长,回收比例低,所以采用的回收算法是标记-清除。这个阶段 GC 回收线程是遍历整个老年代,遇到没有被标记的对象(垃圾)就清空掉相应的内存块,并加入可分配列表。遇到被标记的对象保持原来的位置不动,只是重置其标记位,用于下一次GC。
5、并发重置
Oracle 官方文档中描述这个阶段的工作是:重新调整堆的大小,并为下一次GC做好数据结构支持,比如重置卡表的标位,具体细节有待考证。
三、知识点
1、卡表
CMS 中一个与 YGC 相关并十分重要的数据结构是:卡表(Card Table)。之所以出现卡表这样的一个数据结构是因为:YGC 时为了标记活动标记对象除了遍历 GC ROOTS 之外,别忘了老年代里也可能会引用新生代对象。所以正常来说还要扫描一次老年代,如果是扫描整个老年代这将会随着堆的增大变得越来越慢,特别是现在内存都越来越大了,所以为了提升性能就引入卡表。
卡表提升性能的原理:逻辑上把老年代内存分成一个个大小相等的卡片(Card,论文中提到适合大小是128个字节),然后对每个卡片准备一个与其对应的标记位,并将这些位集中起管理就好像一个表格(mark table)一样,当改写对象引用是从老年代指向新生代时,在老年代对应的卡片标记位上设置标志位即可,通常这样的卡片我们称之为 Dirty Card。这项操作可以通过上面的提到的 Write Barrier 来实现,这样就算对象跨多张卡片也不会有什么问题。卡表通常是用 byte 数组实现的,byte 的值只能取 [0,1] 这两种。所以 btye[i] = 1 就表示第 i + 1 卡片所在内存上有指向新生代引用的老年代对象,这时只要遍历这个卡片上的对象即可。如果每个 card 大小的是128字节(1024位),那卡表就只占整个老年代的 1/1024 之一。所以遍历卡表的时间会远比遍历整个老年代快得多!这其中背后思想就是典型以空间换时间的思路,这种思路在 G1 中也有体现,只不其对应的数据是 remember set 而已。
2、Concurrent Mode Failure
如果 CMS 在清理掉垃圾对象之前,老年代中没有足够的空间存放新产生的对象,就会出现 Concurrent Mode Failure,
四、缺点
- 内存碎片(原因是采用了标记-清除算法)
- 对 CPU 资源敏感(原因是并发时和用户线程一起抢占 CPU)
- 浮动垃圾:在并发标记阶段产生了新垃圾不会被及时回收,而是只能等到下一次GC
然后我产生了一个疑问:既然重新标记可以修正并发标记阶段的变动,那么为何还有浮动垃圾问题?
由于标记阶段是从 GC Roots 开始标记可达对象,那么在并发标记阶段可能产生两种变动:
- 本来可达的对象,变得不可达了。(浮动垃圾)
- 本来不可达的内存,变得可达了。
浮动垃圾是可容忍的问题,而不是错误。那么为什么重新标记阶段不处理第一种变动呢?也许是由可达变为不可达这样的变化需要重新从 GC Roots 开始遍历,相当于再完成一次初始标记和并发标记的工作,这样不仅前两个阶段变成多余的,浪费了开销浪费,还会大大增加重新标记阶段的开销,所带来的暂停时间是追求低延迟的CMS所不能容忍的。
四、日志解读
# 年轻代 GC,使用 ParNew
0.210 [GC (Allocation Failure) [ParNew: 279616K->34942K(314560K), 0.0246537 secs] 279616K->79707K(1013632K), 0.0246999 secs] [Times: user=0.06 sys=0.09, real=0.03 secs]
# 老年代 GC
# 初始计标记,速度很快只用了3.1毫秒
# 372071K:老年代的使用量;699072K:老年代的总容量;413038K:堆的使用量;1013632K:整个堆的容量;0.0003105 secs:耗时
0.526: [GC (CMS Initial Mark) [1 CMS-initial-mark: 372071K(699072K)] 413038K(1013632K), 0.0003105 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
# 启动并发标记步骤
0.526: [CMS-concurrent-mark-start]
0.529: [CMS-concurrent-mark: 0.003/0.003 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]
# 启动预清理步骤,这个阶段会尽可能在重新标记前,处理掉一些在并发标记阶段发生变化的引用关系,从而降低重新标记阶段的停顿时间
# 清理 Eden 区中发生变化的引用(dirty card)
0.529: [CMS-concurrent-preclean-start]
0.530: [CMS-concurrent-preclean: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
# 启动可中断的预清理,这个阶段主要处理 from 和 to 区域对象引用 old gen 的变化,同样也会继续处理 dirty card 的对象引用。这个阶段默认设置的时间是5s,如果执行逻辑超过5s,会自动终止这个阶段,或者当eden区使用内存值小于 CMSScheduleRemarkEdenPenetration,默认 50% 时,也会退出这个阶段。
0.530: [CMS-concurrent-abortable-preclean-start]
0.844: [CMS-concurrent-abortable-preclean: 0.006/0.315 secs] [Times: user=1.34 sys=0.12, real=0.31 secs]
# 重新标记
# 35015K:年轻代当前的使用量;314560K:年轻代的总容量;
0.845: [GC (CMS Final Remark) [YG occupancy: 35015 K (314560 K)]
# Rescan:进行重新标记,耗时 0.0004597 secs
0.845: [Rescan (parallel) , 0.0004597 secs]
# 处理弱引用
0.845: [weak refs processing, 0.0000086 secs]
# 卸载 class
0.845: [class unloading, 0.0004208 secs]
# 清理类级元数据和内部化字符串的符号和字符串表
0.845: [scrub symbol table, 0.0004006 secs]
0.846: [scrub string table, 0.0001479 secs]
# 老年代的使用情况及堆的使用情况
[1 CMS-remark: 677072K(699072K)] 712088K(1013632K), 0.0014893 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
# 启动并发清除,任务是清除那些没有标记的无用对象并回收内存
0.846: [CMS-concurrent-sweep-start]
0.847: [CMS-concurrent-sweep: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
# 启动并发重置,作用是重新设置CMS算法内部的数据结构
0.847: [CMS-concurrent-reset-start]
0.849: [CMS-concurrent-reset: 0.001/0.001 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
垃圾处理器-CMS的更多相关文章
- JVM调优之垃圾定位、垃圾回收算法、垃圾处理器对比
谈垃圾回收器之前,要先讲讲垃圾回收算法,以及JVM对垃圾的认定策略,JVM垃圾回收器是垃圾回收算法的具体实现,了解了前面的前置知识,有利于对垃圾回收器的理解. 什么是垃圾? 垃圾,主要是指堆上的对象, ...
- CMS前世今生
CMS一直是面试中的常考点,今天我们用通俗易懂的语言简单介绍下. 垃圾回收器为什么要分区分代? 如上图:JVM虚拟机将堆内存区域分代了,先生代是朝生夕死的区域,老年代是老不死的区域,不同的年代对象有不 ...
- CMS模板应用调研问卷
截止目前,已经有数十家网站与我们合作,进行了MIP化改造,在搜索结果页也能看到"闪电标"的出现.除了改造方面的问题,MIP项目组被问到最多的就是:我用了wordpress,我用了织 ...
- Kooboo CMS技术文档之五:站点配置管理
站点关系 管理站点间的关系,站点可以有子站点,子站点继承父站点的部分配置数据,同时子站点还可以根据需要,本地化由父站点继承而来的数据.通过继承和本地化,可以让子站点在用最小的改动代价,来完成一个与父站 ...
- Kooboo CMS技术文档之二:Kooboo CMS的安装步骤
在IIS上安装Kooboo CMS Kooboo CMS安装之后 安装的常见问题 1. 在IIS上安装Kooboo CMS Kooboo CMS部署到正式环境相当简单,安装过程是一个普通MVC站点在I ...
- Kooboo CMS技术文档之一:Kooboo CMS技术背景
语言平台 依赖注入方案 存储模型 1. 语言平台 Kooboo CMS基于.NET Framework 4.x,.NET Framework 4.x的一些技术特性成为站点开发人员使用Kooboo CM ...
- Kooboo CMS技术文档之四:Kooboo CMS的站点组成部分
Kooboo CMS本着功能独立分离的原则,将站点分为三部分组成:用户管理,站点管理和内容数据库管理.各个功能之间既可独立使用,也可以容易组成在一起形成一个完整的系统. 用户管理 管理整个系统内的用户 ...
- Kooboo CMS技术文档之三:切换数据存储方式
切换数据存储方式包括以下几种: 将文本内容存储在SqlServer.MySQL.MongoDB等数据库中 将站点配置信息存储在数据库中 将后台用户信息存储在数据库中 将会员信息存储在数据库中 将图片. ...
- zerojs! 造出最好的 CMS 轮子
zerojs是一个基于nodejs.angularjs.git的CMS.在它之上可以继续开发出博客.论坛.wiki等类似的内容管理型系统. 拥抱开发者和社区 层次清晰,高度解耦.前后端即使分开也都是完 ...
随机推荐
- Linux下获取当前的目录,需执行以下命令: $(cd `dirname $0`;pwd)
Linux下获取当前的目录,需执行以下命令: $(cd `dirname $0`;pwd) 其中, dirname $0,取得当前执行的脚本文件的父目录 cd `dirname $0` ...
- tuple必须加上逗号
tuple支持 空 元组 不加逗号 >>> tup4 = () tuple非空的元组必须加上逗号>>> tup4 = (55,)>>> tup4 ...
- CentOS下cpu分析 top
CentOS下 cpu 分析-top 时间:2017-03-20 12:09来源:linux.it.net.cn 作者:IT 一. 前言 我们都知道windows下对各个运行的任务,要通过任务管理 ...
- IDEA 查看类图功能(分析源码的利器)
引言 做过项目开发的童靴,应该会有这样的经历,就是刚进公司领导二话不说直接丢个项目,而且没有任何文档,让熟悉一下,一两周就让上手写代码.打开项目后就看到一堆类源码,完全不知道从何处入手,应该如何分析项 ...
- git使用简单教程-(转自linux人)
什么是Git Git是目前世界上最先进的分布式版本控制系统.最初由Linus Torvalds编写,用作Linux内核代码的管理.如果你是windows用户,看到这里你可能会担心"是不是只能 ...
- CSS(1)基础语法、常见属性
CSS CSS:层叠样式表.主要用于设置HTML页面中的文本内容(字体.大小.对齐方式等).图片的外形(宽高.边框样式.边距等)以及版面的布局等外观显示样式. CSS语法:CSS实例由选择器,以及一条 ...
- Python3 url解码与参数解析
Python3 url解码与参数解析 有些子节点名字直接就是编码后的url,就像下面这行一样: url='dubbo%3A%2F%2F10.4.5.3%3A20880%2Fcom.welab.auth ...
- Go基础结构与类型07---简单的数据类型转换
package main import ( "fmt" "strconv" ) /* 类型转换强化 整型和浮点型可以直接强制转换 字符串和数值的转换用strco ...
- 4D雷达成像技术
4D雷达成像技术 当我们谈及3D捕捉时,总是先想到光学传感器.当我们讨论在第四维度(时间)讨论视觉数据时,倾向于考虑场景数据调度.这些是我们多年来关注激光雷达(LiDAR)和摄影测量,以及用户针对缓慢 ...
- AlexeyAB DarkNet YOLOv3框架解析与应用实践(一)
AlexeyAB DarkNet YOLOv3框架解析与应用实践(一) Darknet: C语言中的开源神经网络 Darknet是一个用C和CUDA编写的开源神经网络框架.它速度快,易于安装,支持C ...