GC算法-标记压缩算法

 

概述

还记得标记清除复制算法的问题么? 堆使用效率低和碎片化问题. 那么有没有能够利用整个堆, 有没有内存碎片化问题的算法呢? 这就是标记压缩算法了.

简单来说, 标记压缩算法就是将堆中的所有活动对象整体向左移, 将对象间的空隙消除.

在GC执行前的内存:

GC执行后的内存:

恩, 就是这么个意思.

实现

如何实现上面的操作呢? 首先, 要将所有活动对象标记出来. 这是标记阶段, 跳过了, 跟标记清除一样操作就行. (这里每个对象都有一个mark属性, true为活动对象)

标记完了, 那就剩下压缩操作了. 如何进行呢?

  1. 遍历堆, 将所有活动对象挪到左边. 但是, 后面有对象引用了前边的对象, 你就找不到新的指针了, 因为那块地址很可能已经被覆盖了.

  2. ....

最后想了想, 还是得老老实实地三步走:

  1. 遍历堆, 将所有对象通过计算得到新的地址并保存

  2. 遍历堆, 将所有子对象的地址更新为新的地址, 同时更新根集合中的指针.

  3. 遍历堆, 将对象集体迁移. 指针的问题都解决了, 可以将对象搬到新家了.

步骤一: 计算所有对象的新地址

// HEAP_START 是堆的开始位置, HEAP_END 是堆得结束位置
obj = HEAP_START
newAddr = HEAP_START
// 遍历所有活动对象
while(obj < HEAP_END){// 非活动对象, 跳过if(obj.mark != true){obj += obj.size;continue;}// 记录新的地址obj.newAddr = newAddrnewAddr += obj.size// 继续遍历obj += obj.size
}

这遍完后, 所有活动对象都保存了自己的新地址, 然后就可以将所有指针的地址进行更新了.

步骤二: 更新所有指针

// 更新根集合中的指针
for(obj in roots){obj = obj.newAddr
}
/*
更新所有活动对象的指针
当然, 这里也可以修改为遍历所有活动对象, 并将指针进行更新. 但是会出现各种重复处理、指针覆盖等问题, 就直接遍历堆了. 
*/
obj = HEAP_START
while(obj < HEAP_END){if(obj.mark != true){obj += obj.size;continue;}// 更新子对象for(child in children){child = child.newAddr}obj += obj.size
}

至此, 所有指针都已经更新完毕, 但是, 对象还没有移动. 只剩下最后一步了, 将对象按照步骤一的规律, 向左排排坐就好啦.

步骤三: 迁移对象

obj = newAddr = HEAP_START
while(obj < HEAP_END){if(obj.mark != true) {obj += obj.size;continue;}// 将obj的数据复制到newAddr处copyData(newAddr, obj, obj.size);// 清空数据, 为下一次GC做准本newAddr.mark = false;newAddr.newAddr = null;// 遍历下一个对象obj += obj.sizenewAddr += obj.size
}

至此, 实现基本完成. 创建对象分配内存的操作与复制算法一样. 这个算法简直是融合了标记清除复制算法的优点, 解决了他们的问题, 不光堆的使用效率变高了, 而且也没有内存碎片的问题了. 但是, 就是, 只不过要对堆进行三次遍历而已. 不过没关系啦, 毕竟有失才有得嘛. 不过是时间换空间了.

而这, 也是标记压缩算法最大的问题了, 执行时间太久了, 标记清除对堆进行一次遍历, 而标记压缩要进行三次. 三倍的时间. 可想而知.

不过也有伟人说了, 算法没有好不好, 只有是否适合. 这几种可达性的算法各有优劣吧.

标记压缩的衍生

Two-Finger算法

将堆的遍历次数减少到两次.

如上图所示, 在第一次遍历的时候, 指针1从前向后寻找空闲地址, 指针2从后向前寻找活动对象, 找到后在原地址中记录新地址, 并将对象进行复制.

第二次遍历就可以将所有对象中的指针进行更新了.

你也发现了, 这个算法如果不想发生内存碎片化, 那就只能令每个对象的空间都是相同的. 而事实上也确实是这样. 强行规定每个对象都占用相同大小的空间, 我不知道这算法有什么应用场景. (原谅我的无知)

其他

还有一些其他的表格算法lmmixGC算法等, 因为这两个我看的似懂非懂, 就不细说了.


标记压缩算法差不多就这么些. 告辞~~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/508433.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GC算法-分代垃圾回收

概述 分代垃圾回收并不是一个新的算法, 而是将之前的回收算法结合利用, 分场景使用. 简单来说, 分代垃圾回收的思路, 就是给每个对象都分配一个年龄, 年龄越大的, 活的越久, 被回收的概率就越小. 经验表明, 大部分对象在生成后马上就成了垃圾. 也就是说, 年轻人要多运动, 没…

GC算法-增量式垃圾回收

概述 增量式垃圾回收也并不是一个新的回收算法, 而是结合之前算法的一种新的思路. 之前说的各种垃圾回收, 都需要暂停程序, 执行GC, 这就导致在GC执行期间, 程序得不到执行. 因此出现了增量式垃圾回收, 它并不会等GC执行完, 才将控制权交回程序, 而是一步一步执行, 跑一点, 再…

不知道写的是啥

刚才起夜, 被黑暗的环境吓到了. 当时有一种四面八方无数双眼睛在看着你, 又好像有什么会突然出现, 然后我赶紧打开灯, 这种感觉立刻烟消云散了, 好像根本没有来过一样. 很显然, 消除这种恐惧感的, 是光. 有没有光对我来说有什么区别呢? 视觉, 有光后, 能够看到周围的环境, 又…

IO多路复用小故事

背景故事 小王住在某城市, 生活并长大. 最近, 小城引进了一个企业, 邮局. 这个邮局可了不得, 只要你花上几角钱, 就可以将一封信送到千里之外的朋友手中. 小王也趁机体验了一把, 得劲. 这天, 小王躺在床上想, 既然这个邮局这么好, 咱何不从中分一杯羹呢? 但是现在人家邮局基…

PHP usort 函数底层排序

引出 最近在一个项目中, 需要对一个数组的顺序进行调整, 允许手动将某一个元素提到数组的开头位置. 在这里, 使用了PHP中的usort函数进行了数组的排序, 代码大致如下: usort($arr, function ($a, $b){// 这里添加了 order 字段, 默认为0, 将order大的提到前边return $b[order…

mac docker搭建开发环境

前言 刚买了一个mac本, 决定搭建一个纯docker的开发环境, 说到做到, 开始踩坑. 搭建 在搭建环境的过程中, 经历了很多错误, 例如为了令两个docker环境可以互通(如: nginx和php-fpm), 尝试了 link, network等等等等方式. 最后发现, 如果想实现两个docker环境的互通, 可以通过…

密钥交换算法: 迪菲-赫尔曼算法

概述 迪菲-赫尔曼算法用于通信双方交换密钥. 还记得之前介绍HTTPS协议的时候, 提到需要先通过对方公钥来进行密钥的交换, 然后再通过密钥对通信内容进行加密. 迪菲-赫尔曼算法就是用于交换密钥的. . 此算法与非对称加密算法不同哦. OK, 一起来看看吧. 引入 在正式介绍迪菲-…

纠错码简介

纠错码是个什么东西 引出 网络中的通信基于TCP和UDP两个通信协议, 这大家都知道的, 什么TCP的三次握手等等, 面试经常被问到. 三次握手是为了保证连接的正确建立. 但是, 在通信的时候, 你如何保证你的消息正确送达了呢? 有人说了, 有收到请求的响应包. 但我说的不是这个, 比…

计算机全加器简单实现

概述 用了这么久计算机, 都知道计算机有一个核心部件叫 CPU, 而 CPU中有一个小部件叫做全加器. 它是用来做什么的呢? 看名字就知道了, 做加法运算用的. 那么如何实现一个全加器呢? 你以为这又是一篇计算机内部原理的文章? 不, 放开那个女孩, 和我一起走进中学物理的课堂. …

天天看底层有什么用

疑问 其实之前我一直有个疑问, 每天工作在应用层, 就算知道 TCP 的传包, 包的校验等等, 在工作中其实是用不到的, 每天看这些东西用什么用呢? 其一, 对工作的内容其实是起不到任何作用的, 其二, 浪费的时间如果将其用在工作上是可以产出更多效益的. 那么, 带给我的到底是什么…

求最大连续子集

问题 前两天看到一道算法题, 想了几天, 然后到网上搜了搜, 基本和我想到的相契合. 来, 题目如下: 给出一个数组, 求出和最大的连续子集. 举个例子: 数组 [1, 2, 3, 4, 5] 那和最大的就是数组本身了. 但是, 如果中间出现负数, 那情况立刻就不一样了, 你需要考虑是否能够将负数…

数据压缩算法

概述 之前在听到数据压缩的时候, 想着肯定是某些高深莫测的算法, 能够完成数据的压缩这种事情, 最近看了看, 嗯, 至少咱还是能看懂的. 无损压缩 众所周知, 不管你是exe, word, txt, dmg等等, 在存储上都是以二进制进行存储的, 所以, 在讨论压缩时, 忽略文件格式即可, 只要将…

何为真何为假

还记得第一个提出日心说的人是谁么? 没错, 哥白尼. 但是在那个年代, 所有人都认为太阳围绕着地球转的年代. 哥白尼的发现打破了大家的常识, 甚至于有过了很多年, 日心说才逐渐被大众所接受. 即使到了现在, 生活中仍然留有地心说的影子, 比如"日出" “日落” 等等, 就…

TCP 三次握手的意义

概述 在网络的传输层协议中, 存在着两大悍将: TCP 和 UDP . 从前, 我傻傻的以为自己对他们虽谈不上精通, 但还是知道的, 但是, 我错了, 我被自己问住了, 我傻了. 啥也不是. UDP (这里为了介绍简单, 就不提数据在传输过程中的失真(纠错码)等情况了. 简单介绍一下, TCP才是今天…

随机数是如何生成的

引出 在现实中, 会有抛硬币猜正反的操作, 硬币要么是正, 要么是反, 在揭晓之前, 我们谁也不知道它现在的状态. 而这, 是因为其中存在着很大的不确定因素, 如抛硬币的力度、抛硬币的角度、接硬币的力度和角度、硬币的重量、当前风速等等. 但是在计算机中, 要想生成一个随机数,…

GO 文档笔记

前言 最开始写 GO 的时候, 发现方法的注释并不支持param, return等参数, 搞得我都不知道该如何给自己的方法写文档说明了. 而且网上搜了搜也没有搜到教程, 甚是郁闷. 今天找到了GO内置的文档工具: godoc. (我用的1.14.3版本貌似不是自带工具了, 需要安装(配置代理): go get g…

长整数的乘法运算

概述 都知道, 计算机中存储整数是存在着位数限制的, 所以如果需要计算100位的数字相乘, 因为编程本身是不支持存储这么大数字的, 所以就需要自己实现, 当然了, 各个编程语言都有大数的工具包, 何必重复造轮子, 但我还是忍不住好奇他们是如何实现的, 虽然最终没有翻到他们的底层…

如何将数字转换成口语中的文本串

概述 今天突发奇想, 写一个将数字转换成中文字符串的函数. 并不是将 1234 转成 1234 , 而是将 1234 转成 一千二百三十四. 本来以为很简单, 写下来之后发现还是有些坑的. 尝试 因为我是在写完最终版本, 回过头来整理的这篇文章, 所以中间很多尝试的步骤会有所遗漏. 以下简单…

设计模式故事会

想着用讲故事的方式对各个设计模式进行一个稍微形象一些的描述. 当然, 某些因为没有想到特别好的例子, 可能比喻的并不是那么的恰当, 望赐教. 绞尽脑汁啊, 尽力贴近设计模式其本来的意图. 主要为了加强一些自己对设计模式的理解. 进入正题: 小王是一名心怀梦想的90后青年, 他独…

有了 elseif 为什么还要 switch case

引出 你有没有想过既然有了if elseif, 为什么还要设计一个switch case的语法出来呢? 按理说, 一个语言的设计角度来说, 关键词越少越好吧, 而且多出来一种选择分支也没有看出太大用处. 以下几种switch case均可以写成if else的形式(java 代码): // 形式一 switch(a){case 1:…