分享好友 最新动态首页 最新动态分类 切换频道
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
最新文章
百度https不收录 百度为何不收录HTTPS站点?揭秘收录难题
标题:《直面挑战,破解“百度HTTPS不收录”之谜》在当今数字化时代,搜索引擎优化(SEO)已成为企业网站获取流量、提升品牌知名度的关键途径然而,近年来不少网站运营者遭遇了“百度HTTPS不收录”的难题,这一现象不仅影响了网站的正常曝
计算机中如何按照成绩排名,电脑上名次怎么排序:怎么在excel 中进行成绩名次排序...
EXCEL如何对自动排名的结果再进行自动排序?1、首开excel表格,按住ctrl连续。2、然后筛选一些人名后,发现时序号不连续了。3、右侧创建一个序号列,输入=subtotal,选择3。4、输入完整公式=subtota
解读《金融资产管理公司不良资产业务管理办法》
综述:近日,国家金融监督管理总局发布了《金融资产管理公司不良资产业务管理办法》(以下简称《办法》),以规范和加强金融资产管理公司(AMC)在不良资产业务中的操作与管理,优化资源配置,防控风险。《办法》是针对不良资产领域的全面
ThinkPadS2
如何看ThinkPadS2优缺点曝光分析?真实情况如何?此款ThinkPadS2的网友有不少,这回就给大家介绍一下,这个新出来的,很受欢迎,客服的服务态度很好,回复问题也很及时,解决了我的困惑。【点击查看ThinkPadS2活动分享】ThinkPadS2价格参考
视频号互选广告能力升级,一文玩转应用推广-杭州微信小程序开发为你呈现
产品升级PRODUCT PROMOTION视频号互选广告现已全面支持下载能力投放。通过视频号互选广告丰富的创作者及组件营销能力,可满足广告主在应用下载、品牌营销、线索转化等各类不同的内容营销目标,助力电商平台、游戏、网服等多行业客户提升获
高清美女写真一键生成!探索最强AI绘画工具
DeepAI:这是功能强大的图像生成平台,用户可以通过输入深度描述来生成高质量图像。尽管效果满意,但高级功能需付费,可能让一些预算有限的用户感到不便。GANPaint Studio:这款工具的技术基础同样是生成对抗网络,允许用户进行图像细节的
梦幻西游永生神兽才是真爱?这份神兽的价格调研你且收下!
近期,新年“神兜兜”礼包上架,不少小伙伴都开启了神兽“兑换”模式,常规神兽也或多或少有了一定的涨幅。在小伙伴的建议下,小编对梦幻西游电脑版全服、大烟花区的神兽做了一个简单的调研,给大家理一理如今的神兽市场。  一、平民神兽
Python爬虫必备工具大盘点
  在当今信息化时代,网络爬虫成为获取大量数据的一种重要手段。而要开发一款高效、稳定的网络爬虫,离不开一系列强大的爬虫工具。本文将为您盘点一些爬虫必备的工具,帮助您快速构建出具有实际价值的网络爬虫。让我们一起探索吧!  一
企业快速开通云电脑政企版使用云上办公
天翼云电脑是云计算技术和终端相结合的创新型产品。依托中国电信优质云网资源,结合自主研发的CLINK数据安全传输协议,具备多重数据安全防护机制,实现安全高效的云电脑使用体验。提供一键部署、灵活可配、集中管控能力,广泛应用于办公、
王者荣耀主播杯虚拟人物,游戏内外的英雄魅力
摘要:王者荣耀主播杯呈现虚拟人物魅力,游戏内外英雄风采展现。通过主播与虚拟人物的互动,展现游戏世界的精彩战斗和英雄魅力。观众们可以感受到游戏带来的 *** 和乐趣,同时欣赏到虚拟人物的独特魅力和个性风采。这是一场融合了游戏与文
相关文章
推荐文章
发表评论
0评