分享好友 最新动态首页 最新动态分类 切换频道
1. 垃圾回收基本操作
2024-12-27 05:50

原文:http://anduo.me/2017/03/04/gc_cms/#31_Initial_Mark


1.1.1 GCRoots

GCRoots大致有如下几种: 
当前执行函数的局部变量和输入参数
正在工作的线程
被加载类的静态块
JNI(Java Native Interface)引用

1.1.2 标记算法实现

标记算法的算法实现,可参考如下伪码

1.1.3 Card Marking

上述的朴素的标记算法并不适用于并发GC的情况。
并发标记操作在进行的过程中,如果一个对象及其子对象已经被标记,而此时用户线程操作这一对象,为其新引用了一个将要被回收的对象时,普通的标记算法可能无法标记到这一对象。但是Card Marking算法则可以利用将这一个块(Card)标记为为“脏块”(Dirty Card)的方式记录下这个块,等待GC Collector后续处理。

本节介绍的是最简单的Card Marking算法,更多的实现请查看Card Marking算法。

1.1.4 三色标记算法(Tri-color Marking Algorithm

三色标记算法的数据结构中包含有三个集合:White Set, Black Set和Gray Set。
– 白色集合对象:需要被回收的对象。
– 黑色集合对象:没有对白色集合对象的外部引用,并且是GC Root可达的对象。这些对象将不会被回收。
– 灰色集合对象:集合中的对象全都是GC Root可达的对象,但是正在扫描或正在等待扫描其对“白色集合对象”的引用,这些对象也不会被回收,并且会在扫描结束之后被移入黑色集合。

在大多数算法实现中,黑色集合初始是空,灰色集合中保存有与GC Roots对象直连的所有老年代对象,白色集合中包含有其他对象。内存中的任意对象在任意时间都仅存在于这三个集合当中的一个。

算法步骤
1. 从灰色集合中取出一个对象放入黑色集合
2. 遍历第1步取出的对象的所有白色集合对象引用,并将它们移入灰色集合。这保证了这个对象和它的引用对象都不会被GC
3. 重复上述两步,直到灰色集合为空

由于非GC Root直接可达的节点都被加入到了White Set,并且对象只能从白色集合移动到灰色集合,从灰色集合移动到黑色集合,所以算法体现了一个重要特性-黑色集合中的对象不会引用到白色集合中的对象。这就保证了在灰色集合为空时,我们可以放心地释放白色空间中的对象。这被称作三色不变式(The Tri-color Invariant)。

1.2.1 清理(Sweep)

清理算法会遍历整个内存,释放(Free)掉内存区域中所有未被标记的对象,同时重置标记过的对象的标记位。标记-清理算法使用最简单
释放内存是由一个叫做free-list的数据结构来实现的,它会记录每一个空闲的空间地址和他的大小。维护这些free-lists会给创建对象的内存分配带来额外的开销。除此之外,这种方法还有另外一个缺点–可能存在有大量的空闲空间,但是却没有一个比较大的连续空闲空间(过多内存碎片,当内存中需要放入一个大对象时,系统将会分配内存失败。(OutOfMemoryError

清理算法的代码实现可参考如下伪码:

1.2.2 整理(Compact)

整理步骤是将所有被标记的存活对象按顺序移动到内存的前部,它一般配合标记算法和清除算法一起使用(标记-清除-整理算法)。由于压缩的过程中清理了内存碎片,所以这个算法可以弥补标记-清除算法的短处。但是这个整理的步骤也会影响到算法的性能,因为算法需要把所有的对象拷贝到一块新的内存空间,同时还要改变他们的引用。

目前常用的整理算法分为两种,Table-based compaction和LISP2

1.2.2.1 Table-based compaction

Table-based compaction算法是由Haddon和Waite在1967年提出来的。算法的特点是不需要额外的空间。
算法步骤

  • 第一步是用mark算法先将所有的存活对象标记出来。
  • 第二步是遍历整个堆,将对象移动到最初的free空间中(向前移动,并且将对象的原始起始地址和移动的字节数记录在一个叫break table的数据结构中。(break table会不断的移动,使用的是unused空间
  • 第三步是对break table按照对象初始地址排序。排序时间复杂度O(n logn),n是存活对象数。
  • 第四步是修改移动过后的对象中的指针引用,如果引用指针个数总数为m,一次指针查找的复杂度是logn(二分查找,则算法复杂度为O(m logn)

详细信息可查看A compaction procedure for variable-length storage elements。(论文介绍了break table是如何通过移动来找到一块合适的unused空间

1.2.2.2 LISP2算法

LIST2算法是一种时间复杂度与堆大小成正比的compact算法,目前被应用于ParallelScavenge算法中。
LISP2算法需要每一个对象都存在一个叫“forwarding pointer”的指针,它被用来临时保存对象在compact之后被移动到的位置。同时,LISP2算法在进行的过程中还需要用到两个全局的指针–free指针和live指针。其中,free指针用来指向当前空闲的区域,live指针用来指向当前操作的存活对象。

算法步骤
1. 计算对象移动后的位置:先让free指针和live指针都指向堆的头部,如果当前live指针指向了一个存活的(被标记的)对象,那么将free指针指向的值赋予live指针指向对象的forwarding pointer区域,然后将live指针和free指针的值都加上sizeof(current_obj)。如果live指针当前指向的不是一个存活对象,那么就逐步移动live指针,直到它指向了一个存活的对象为止。整个步骤结束于live指针指向了堆的结尾。
2. 更新所有指针:与第一步一样,找出每一个存活对象,将这些对象里面的对象对应引用变量值修正为被引用对象的forwarding pointer值。
3. 移动对象:还是需要找出所有存活对象,将对象的数据移动到forwarding pointer所指向的区域。

算法的伪代码如下

这个算法一共要遍历三次堆,所以时间复杂度和堆空间的大小成正比。为O(m),m为堆空间大小。

1.2.3 复制(Copy)

复制算法很像压缩算法,他们都会移动所有的存活对象并且修改对象内部引用的指针地址。而复制算法的不同之处则在于它会将这些对象移动到一个新的区域内。标记复制算法有一个非常大的优点–复制操作可以和标记操作在同一时间进行。它的缺点也很明显,他需要一个足够容纳一次GC后存活对象大小的内存区域。

复制算法的一种实现是Cheney算法,它是由C.J. Cheney在1970年在论文A nonrecursive list compacting algorithm提出的。

算法步骤
主函数
– 将roots对象加入tospace(调用Copy函数)。
– 将tospace看做一个Queue,从头至尾遍历存在的数据节点,针对数据节点中的每一个指针,调用Copy函数。

Copy函数
– 如果该对象已经被移动到了tospace(对象中的forworded字段为1,那么直接返回对象在tospace中的首地址。
– 如果对象尚未移动,则将对象的数据移动到tospace,free指针后移对象的长度个单位,并且把对象的forwarded域置为1,最后返回移动后的地址。

算法实现(Cheney’s algorithm


– 算法中forwarded字段位于Block Header中,值为1表示块已经被移动to space。
– forwarded=1标示块中数据区存放了fwdaddr,位于块数据区的第一个word中。
– 本算法遵循三色不变式

上述的几种垃圾回收基本操作可以组成的三种算法:标记-清除,标记-整理和复制算法。它们的基本信息如下表所示。

 mark-sweepmark-compactcopying速度中等最慢最快空间开销少(但会堆积碎片)少(不堆积碎片)通常需要活对象的2倍大小(不堆积碎片)移动对象?否是是

关于时间开销
– mark-sweep:mark阶段与活对象的数量成正比,sweep阶段与整堆大小成正比
– mark-compact:mark阶段与活对象的数量成正比,compact阶段与活对象的大小成正比
– copying:与活对象大小成正比

如果把mark、sweep、compact、copying这几种动作的耗时放在一起看,大致有这样的关系
– compaction >= copying > marking > sweeping
– marking + sweeping > copying

虽然compactiont与copying都涉及移动对象,但取决于具体算法,compact可能要先计算一次对象的目标地址,然后修正指针,然后再移动对象;copying则可以把这几件事情合为一体来做,所以可以快一些。
另外还需要留意GC带来的开销不能只看collector的耗时,还得看allocator一侧的。如果能保证内存没碎片,分配就可以用pointer bumping方式,只有挪一个指针就完成了分配,非常快;而如果内存有碎片就得用freelist之类的方式管理,分配速度通常会慢一些。
在分代式假设中,年轻代中的对象在minor GC时的存活率应该很低,这样用copying算法就是最合算的,因为其时间开销与活对象的大小成正比,如果没多少活对象,它就非常快;而且young gen本身应该比较小,就算需要2倍空间也只会浪费不太多的空间。
而年老代被GC时对象存活率可能会很高,而且假定可用剩余空间不太多,这样copying算法就不太合适,于是更可能选用另两种算法,特别是不用移动对象的mark-sweep算法。

内存块的数据结构如下图所示,其中每一个区域的作用如下
– Block Data Area:1. 这一块是用户程序所使用的内存;2. 大小可变,取决于用户程序一开始申请了多少空间;3. 块分配时,返回的指针指向的是- – Block Data Area的头部,用户程序也只能感受到Block Data Area中的内容。
– Block Header:1. 这一块内容是堆的内部程序锁使用的空间;2. 这一块大小是固定的;3. 用户程序无法感知到这一块内存。
– nWords:1. 内容是Block的大小(data area + header;2. 块的最大大小被nWords的位(bit)数所约束,比如说24位的nWords可以支持的最大Block是16,777,215个Words(word大小自定义
– Control bits:包含了垃圾回收时所需要用到的一些信息(比如标记信息

单个大空闲块的组织结构如下图所示,所有被使用的块都存放于堆的顶部,而未被使用的快则是被某一个块所控制。使用移动对象类型的垃圾回收算法(如mark-compact,copying)可以保证堆空间中的空闲空间总是以该结构的形式组织。

这种组织结构的优点:1. 对象分配速度快;2. 堆中没有内存碎片,可以分配更大的对象。
缺点:需要可以移动对象的垃圾回收算法(速度慢,对象需要移动,指针需要调整

2.2.2 分配算法

这种单个空闲空间的堆空间分配内存非常简单,可以直接使用bump-the-pointer的算法,如下图,移动一下指针就完成了内存分配。

算法实现的伪代码如下,算法的时间复杂度是O(1)

2.3.1 堆结构

Free List组织结构的对结构如下图所示,被使用的块和空闲的块在堆中是相互交错着排列的,另外一个要点就是空闲的块会被连接成一个叫做Free List的链表。

这种组织结构的优点:适用不需要移动对象的垃圾回收算法(对象回收速度更快,因为不需要移动被使用的对象,也不需要调整指针
缺点:1. 分配算法更加复杂,耗时也更长;2. 存在有内存碎片,会限制分配对象的最大值

2.3.2 分配算法

Free List组织结构下存在有两种分配算法:First-Fit分配算法和Best-Fit分配算法。顾名思义,First-Fit分配算法就是在Free List中找出第一个适合分配的空间,而Best-Fit分配算法则是在Free List中找出最接近被分配对象大小的Free块,并将这个块分配给这个对象。这两种算法的伪代码如下所示

First-Fit Allocation Algorithm

Best-Fit Allocation Algorithm

  • 当堆中的空闲块不够新申请空间的大小时,将会进行一次垃圾回收,然后在尝试分配
  • 如果空间还是不够,则会尝试增大堆的大小,然后再尝试分配
  • 最后如果空间还是不够,则会抛出OutOfMemoryError

CMS算法是JVM中老年代常用的垃圾回收算法,全称是Concurrent Mark Sweep算法,即并发标记-清除算法。算法的执行步骤如下图所示,共有六个步骤。

CMS算法中两个会触发Stop the World事件中的一个,这个阶段会标记所有与GC Roots直接相关联的对象,以及被存活的青年代对象所直接引用的对象。

并发标记,顾名思义,它是并发的执行标记任务的,这也就意味着GC在运行的过程中用户的应用线程并不会停止工作。该阶段GC收集器会从第一步“初始标记”中所标记出来的对象开始逐步遍历这些对象(与GCRoot直接相连或与存活的青年代对象直接相关联的对象)的所引用的对象,并将这些被引用的对象加上标记。
需要注意的是,这一步中,会漏掉一下老年代的存活对象,这是因为在并发的过程中,用户应用线程可能会对老年代的对象产生引用上的改变。某一些被改变的标记可能会被遗漏。

并发预清理是Java1.5被加入进来的。主要目的是减少重标记(Remark)步骤Stop-the-World的时间。这一步同样也是并发的,不会停止用户应用线程。在前面的并发标记中,一些引用被改变了。当某一块块(Card)中的对象引用发生改变时,JVM会标记这个空间为“脏块”(Dirty Card)。

在预清理阶段,JVM根据之前记录的这些“脏对象”重新标记了他们新的可达对象。这一步结束后空间重新进入clean状态。另外,一些必要的最终重标记之前的准备步骤也会在这一步做好。

预清理步骤将会不断重复一直到Eden区的占用量达到某个指定的阈值。设定这个阈值作为结束条件的原因主要是为了防止YoungGC产生的Stop-the-World和下一阶段的Remark同时产生,导致系统产生一个更长的停滞。设定了这个阈值之后基本可以保证Remark阶段可以在两次YoungGC之间进行。

这是CMS算法中第二个会触发Stop-the-World事件的步骤,由于前一步是一个并发的步骤,预清理的速度可能会赶不上用户应用对对象改变的速度,所以需要一个Stop-the-World的暂停来完整的标记所有对象结束整个标记阶段。
通常CMS会在年轻代为空时来运行重标记阶段,以此避免一个接一个的Stop-the-World阶段。

这一阶段程序并发地工作,目的是移除所有不用的对象,并且重新声明内存空间的归属等候将来使用。

并发地重置所有算法需要的内部数据结构,为下一次GC做准备。

3.7.1 Concurrent Mode Failures

当CMS算法在并发的过程中堆空间无法满足用户程序对新空间的需求时,Stop-the-World的Full GC就会被触发,这就是Concurrent Mode Failures,这通常会造成一个长时间停顿。这种情况通常是因为老年代没有足够的空间供青年代对象promote。(包括没有足够的连续空间

3.7.2 CMS相关JVM参数

  • -XX:+UseConcMarkSweepGC:激活CMS收集器,默认情况下使用ParNew + CMS + Serial Old的收集器组合进行内存回收,Serial Old作为CMS出现“Concurrent Mode Failure”失败后的后备收集器使用。
  • -XX:CMSInitiatingOccupancyFraction={x}:在老年代的空间被占用{x}%时,调用CMS算法对老年代进行垃圾回收。
  • -XX:CMSFullGCsBeforeCompaction={x}:在进行了{x}次CMS算法之后,对老年代进行一次compaction
  • -XX:+CMSPermGenSweepingEnabled & -XX:+CMSClassUnloadingEnabled:让CMS默认遍历永久代(Perm区
  • -XX:ParallelCMSThreads={x}:设置CMS算法中并行线程的数量为{x}。(默认启动(CPU数量+3) / 4个线程。
  • -XX:+ExplicitGCInvokesConcurrent:用户程序中可能出现利用System.gc()触发系统Full GC(将会stop-the-world,利用这个参数可以指定System.gc()直接调用CMS算法做GC。
  • -XX:+DisableExplicitGC:该参数直接让JVM忽略用户程序中的System.gc()
  • GC算法基础及实现:https://plumbr.eu/handbook/what-is-garbage-collection
  • Oracle文档:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/toc.html
  • CMS算法:http://insightfullogic.com/2013/May/07/garbage-collection-java-3/
  • GC Tuning: https://plumbr.eu/handbook/gc-tuning-measuring
  • 三色标记: https://en.wikipedia.org/wiki/Tracing_garbage_collection#Tri-color_marking
  • 内存管理wiki: http://www.memorymanagement.org/glossary/
  • Rednaxelafx’s Blog:  http://rednaxelafx.iteye.com/blog/362738
  • rit GC资料:https://www.cs.rit.edu/~ark/lectures/gc/index.html
  • memory barrier: https://www.kernel.org/doc/Documentation/memory-barriers.txt

Warning : Missing argument 1 for cwppos_show_review(), called in /home/u1038/ace/workspace/php/appcode/webroot/htdocs/wp-content/themes/flat/content-single.php on line 29 and defined in /home/u1038/ace/workspace/php/appcode/webroot/htdocs/wp-content/plugins/wp-product-review/includes/legacy.php  on line  18
最新文章
vivo iQOO Neo 855竞速版线刷宝完美ROOT工具教程
vivo iQOO Neo 855竞速版怎样root?root的方式有很多种,一般解锁BL后通过Twrp刷入root补丁或者带root的卡刷包,也可以单刷内核boot来获取root权限。还有一种方式就是直接刷入内置root权限的系统,这样开机后直接拥有root权限,而且不怕恢
营销活动策划方案怎么做精选5篇
在日常学习、工作或生活中,大家总少不了接触作文或者范文吧,通过文章可以把我们那些零零散散的思想,聚集在一块。那么我们该如何写一篇较为完美的范文呢?接下来小编就给大家介绍一下优秀的范文该怎么写,我们一起来看一看吧。一:__宾馆
【5261(电信4G)搜狗手机输入法下载】酷派5261 电信4G搜狗手机输入法12.1.1免费下载
搜狗输入法,拥有超大中文词库,输入更加精准,智能。搜狗智能旺仔带你用表达,斗图,妙语,输入更加有趣。******特色功能******【搜狗专属超大词库】搜狗多年积累,中文系统词库,输入首选更准确【语音输入】更快更准的语音输入,没有网络
网络营销的“软”策略
软硬是相对的,如果把报纸、杂志、电视、广播以及网络等媒体看得见、听得着的广告行为视为硬广告的话,那么具有互动性和参与性且的广告行为则可称作是软广告了,众所周知的“报纸软文”便是其一。 “软”营销的重要特点就是强调互动性和参
谷歌广告完整指南:19种细分广告类型及其投放指导
谷歌广告是跨境电商卖家进行业务推广的强大利器。借助谷歌广告,卖家可以在世界热门网站(例如,谷歌和YouTube)上开展营销活动,轻松触达全球消费者。然而,谷歌广告有多种类型,每一种类型适合不同的场景。如果无法掌握投放技巧,广告很
高清美女写真一键生成!用AI绘画工具轻松打造你的AI女友
DALL-E是一款由OpenAI开发的生成模型,能够将文字描述转化为图像。它支持多种风格,结果常常令人惊艳。然而,DALL-E 可能需要强大的计算资源,并且生成图像的时效性较慢,一些用户可能没有耐心等待。Midjourney是一个社区驱动的AI绘画工具
详细分析搜狗SEO推广托管,助力企业高效抢占网络市场
随着互联网的飞速发展,企业之间的竞争日益激烈,如何在众多竞争者中脱颖而出,成为广大企业关注的焦点。而SEO推广托管作为一种新兴的网络营销方式,正逐渐受到企业的青睐。本文将为您深度解析搜狗SEO推广托管,帮助您了解其优势、操作方法
编程利器,笔记本电脑排行榜TOP榜解析
一、概述对于编程爱好者来说,一款适合编程的笔记本电脑至关重要。本文将结合性能、便捷性、舒适度等多方面因素,为大家解析当下编程笔记本电脑排行榜。在这份排行榜中,列出了最受欢迎的适合编程工作的笔记本电脑型号和他们的主要特点,供
/精准营销 全网覆盖
数据显示,86.9%的企业网站不符合搜索引擎技术架构要求,企业网站建设的不规范性已经成为企业电子商务发展的更大制约因素,企业主动实施”符合搜索引擎架构”的网站建设刻不容缓。2013年《中国搜索引擎市场份额报告》显示,搜索引擎营销规
计算机毕业设计项目之基于Python的大数据技术的家政服务预约平台设计与实现-爬虫-可视化大屏
《[含文档+PPT+源码等]精品基于Python的大数据技术的家政服务预约平台设计与实现-爬虫》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功以及课程答疑与微信售后交流群、送查重系统不限次数免费
相关文章
推荐文章
发表评论
0评