自动内存管理算法 —— 标记和复制法
最近阅读了《垃圾回收算法手册》这本经典的书籍,借此机会打算写几篇内存管理算法方面的文章,也算是自己的总结吧。
—— 题记
自动内存管理系统
自动内存管理主要面临以下三个方面的任务:
1.为新对象分配内存空间
2.确定“存活”对象
3.回收“死亡”对象所占用的内存空间
其中任务1一般称作“自动内存分配”(Memory Allocation ,下文简称 MA),任务2、3便是常说的“垃圾回收器”(Garbage Collect 即 gc ,下文简称GC)。一般来说,为了降低自动内存管理系统设计的复杂性和稳定性,一般会采用单线程设计方式,也就是说MA和GC不能同时进行,这样一来就解释一些托管代码在有大量GC的情况下程序卡顿的情况,因为在GC线程运行时,MA线程会等待,即托管代码暂停执行,直到GC结束,如果此时GC运行时间较长,那么程序卡顿的情况就会较为明显。
标记回收法
基本的垃圾回收策略主要有四种:标记-清扫、标记-整理、复制回收法、引用计数,这里把前两种归纳为标记法。标记法回收策略分为两个过程:标记和回收,其中标记阶段是指:从根集合开始用DFS搜索方式标记每个存活对象(即对象可达:从根集合开始可以找到该对象),回收是指:遍历堆,把未标记的对象(即不可达对象)当做垃圾回收。无论采用何种回收策略,一般复制器的工作流程是类似的,这里在先介绍一下赋值器的伪代码:
New():
ref <—— allocate() //在堆中分配,可能会因内存碎片或者内存不足导致分配失败
if ref = null
collect() //执行回收策略
ref = allocate()
if ref = null
error "out of memory"
return ref
/*
所有标记回收策略均满足如下范式
先标记,后回收
其中atomic是原子操作关键字,
标识该方法以原子操作的方式执行完毕
*/
atomic collect():
markFromRoots()
sweep(HeapStart , HeapEnd)
一.标记-清扫算法
标记-清扫是一种十分简单的回收策略,其中的标记阶段是标记法中通用的策略,这里的“清扫”只是回收的最简单实现。其主要思路:
1.从“根集合(Roots)”开始,DFS遍历所有的对象,标记其是存活对象。【从Roots出发可以访问的对象可简单看做是可达对象,近似等于存活对象】
2.线性遍历堆,回收未标记的对象。【未标记即不可达,可看做非存活对象】
注:“Roots”是一个堆中的有限集合,复制器可以直接访问到,通常包括:静态、全局存储、线程本地存储,简单可以看成“对象图”中的入口对象集合
下面贴出标记-清扫最简单的伪代码:
//标记阶段
markFromRoots():
initList(workList) //在DFS过程使用一个list作为缓冲
for each fld in Roots //遍历所有Roots对象,然后由根对象DFS到每个可达对象,实现对存活对象的标记
ref <- *fld
if ref ~= null
setMarked(ref) //设置标记,这里有很多种实现,如写入对象头部,位图,字节图等
add(workList)
mark()
initList():
workList <- empty //常规的DFS算法
mark():
while not isEmpty(workList)
ref <- remove(workList)
for each fld in Points(ref)
child <- *fld
if child != null && not isMarked(child)
setMarked(child)
add(workList) ============================================ //回收阶段
sweep(start , end):
scan <- start
while scan < end
if isMarked(scan)
unsetMarked(scan)
else
free(scan)
scan = nextObj(scan)
就像上面的伪代码只是标记-清扫最简单的描述,实际上它存在很多性能问题的,如时间局和空间局部性、无法高效利用高速缓存、容易缺页等,所以这里只做最简单的说明。
二.整理法
即使算法相对完备的“标记-清扫”回收策略也无法避免“内存碎片”问题,因为该算法在“清扫”过程中仅仅简单地遍历堆,直接释放“不可达对象”,这样一来必然造成内存碎片,了解操作系统的coder肯定清楚,一旦内存碎片过多是一件十分可怕的事情,很可能造成有内存却无法使用的情况,最终导致内存崩掉……所以这时候就出现了“标记-整理”回收策略,它分为“标记“和”“ 整理”连个部分,其中标记部分和“标记-清扫”算法一致,区别在于回收策略上。
“标记-整理”法的整理过程有好几种算法,大都倾向于将存活对象整理到堆的某一端,这里介绍一种运用较为广泛的算饭“List 2”算法,该算法主要分为三部分:
1.计算整理后“存活对象”在堆中对应的地址
2.更新复制器的根及被标记对象的引用
3.真正移动对象到其对应的新地址
//“整理”算法的主体过程
Compact():
computeLocations(HeapStart , HeapEnd ,HeapStart) //计算整理后“存活对象”在堆中对应的地址
updateReferences(HeapStart ,HeapEnd) //更新引用
relocate(HeapStart , HeapEnd) //引动对象到最终位置 computeLocations(start , end , toRegion):
scan <- start
free <- toRegion
while scan < end //从头到尾扫描堆,找出被标记对象
if isMarked(scan)
forwardingAddress(scan) <- free //forwardingAddress(scan) 表示该对象的“转发地址”即整理后的地址,
free <- free + size(scan) //可能在该对象头信息中记录,也可能以字节图等形式记录
scan <- scan + size(scan) updateReferences(start , end):
for each fld in Roots //更新根引用地址
ref <- *fld
if ref != null
*fld <- forwardingAddress(ref)
scan <- start while scan < end //扫描堆,更新其他对象信息
if isMarked(scan)
for each fld in Pointers(scan)
if *fld != null
*fld <- forwardingAddress(*fld)
scan <- scan + size(scan) relocate(start , end):
scan <- start
while scan < end
if isMarked(scan)
dest <- forwardingAddress(scan)
move(scan , dest) //真正移动对象
unsetMarked(dest) //去除记录的转发地址信息
scan <- scan + size;
“标记-整理”回收策略较大的问题在于执行效率,因为大都需要多次扫描堆,容易造成gc卡顿时间较长,再者类似list 2 这种用对象头部记录转发地址信息的方式,也在一定程度上造成空间浪费。
三.复制式回收
相对于“标记-整理”策略需要多次遍历堆进行回收,“复制式回收”只需要遍历一次堆,同时也清理了”内存碎片“,并保证了对象在堆中的相对顺序(提高了程序的空间局部性)。但是它有个致命的缺点是堆的可利用空间只有一半。下面是算法的主要思想:
1.将堆空间平均分为两半(对象区和空闲区)
2.遍历对象区,并把对象顺序移到空闲区
3.遍历结束,对象被整理到空闲区,释放(即直接丢弃)原对象区
atomic collect():
flip() //分割半区
initList(workList) //作为缓存栈
for each fld in Roots
process(fld) //先处理根域
while not isEmpty(workList) //处理worklist中对象
ref <- remove(workList)
scan(ref)
//将堆平均分成两部分,假设HeapStart是存储数据的堆
flip():
extent <- (HeapEnd - HeapStart) / 2
top <- HeapStart + extent
free <- top //扫描给定对象的指针域
scan(ref):
for each fld in Pointers(ref)
process(fld) //更新对象的指针地址
process(fld):
fromRef <- *fld
if fromRef != null
*fld <- forward(fromRef)
//转移对象
forward():
toRef <- forwardingAddress(fromRef) //forwardingAddress 记录了对象的转移地址
if toRef != null //判断对象是否已经被转移
toRef <- copy(fromRef)
return toRef //将对象拷贝到堆的另一个半区
copy(fromRef):
toRef <- free
free <- free + size(fromRef) //移动空闲指针
move(fromRef , toRef)
forwardingAddress(fromRef) <- toRef //记录转移地址
add(workList , toRef)
return toRef
总结
基本的基于”标记“的内存管理策略主要就是上面两种算法,其实优秀的内存管理策略肯定不会仅仅只使用某种单一策略,它们可能更倾向于多种策略同时使用,比如在内存充足时可能就直接使用“复制式回收策略”,内存不足时切换成“标记-回收策略”,当然每种算法都会有各种优化策略,基本就是基于“空间和时间局部性”做优化,可以很大程度提升回收器的效率,减少卡顿时间。
自动内存管理算法 —— 标记和复制法的更多相关文章
- 垃圾回收算法手册:自动内存管理的艺术 BOOK
垃圾回收算法手册:自动内存管理的艺术 2016-03-18 华章计算机 内容简介 PROSPECTUS 本书是自动内存管理领域的里程碑作品,汇集了这个领域里经过50多年的研究沉积下来的最佳实践,包含当 ...
- JVM自动内存管理学习笔记
对于使用 C.C++ 的程序员来说,在内存管理领域,他们既是拥有最高权力的皇帝又是从事最基础工作的劳动人民——拥有每一个对象的“所有权”,又担负着每一个对象生命开始到终结的维护责任.对于 Java 程 ...
- JVM自动内存管理机制——Java内存区域(下)
一.虚拟机参数配置 在上一篇<Java自动内存管理机制——Java内存区域(上)>中介绍了有关的基础知识,这一篇主要是通过一些示例来了解有关虚拟机参数的配置. 1.Java堆参数设置 a) ...
- JVM自动内存管理机制--读这篇就GO了
之前看过JVM的相关知识,当时没有留下任何学习成果物,有些遗憾.这次重新复习了下,并通过博客来做下笔记(只能记录一部分,因为写博客真的很花时间),也给其他同行一些知识分享. Java自动内存管理机制包 ...
- [翻译]理解Unity的自动内存管理
当创建对象.字符串或数组时,存储它所需的内存将从称为堆的中央池中分配.当项目不再使用时,它曾经占用的内存可以被回收并用于别的东西.在过去,通常由程序员通过适当的函数调用明确地分配和释放这些堆内存块.如 ...
- 【深入理解Java虚拟机】自动内存管理机制——垃圾回收机制
Java与C++之间有一堵有内存动态分配和垃圾收集技术所围成的"高墙",墙外面的人想进去,墙里面的人却想出来.C/C++程序员既拥有每一个对象的所有权,同时也担负着每一个对象生 ...
- JAVA之自动内存管理机制
一.内存分配 1.JVM体系结构 2.运行时数据区域 3.内存分配二.内存回收 1.垃圾收集算法 2.垃圾收集器三.相关参考一.内存分配JVM体系结构 在了解自动内存管理的内存分配之前,我们先看下JV ...
- 深入理解Java虚拟机(自动内存管理机制)
文章首发于公众号:BaronTalk 书籍真的是常读常新,古人说「书读百遍其义自见」还是很有道理的.周志明老师的这本<深入理解 Java 虚拟机>我细读了不下三遍,每一次阅读都有新的收获, ...
- JVM | 第1部分:自动内存管理与性能调优《深入理解 Java 虚拟机》
目录 前言 1. 自动内存管理 1.1 JVM运行时数据区 1.2 Java 内存结构 1.3 HotSpot 虚拟机创建对象 1.4 HotSpot 虚拟机的对象内存布局 1.5 访问对象 2. 垃 ...
随机推荐
- 图学java基础篇之集合
(本文部分图片引用自其他博客,最后有链接,侵删.由于笔记使用markdown记录,格式可能不是太好看,见谅) 集合结构 红字为java.util包下的,绿字为concurrent包下扩展的与并发相关的 ...
- 记一次开发过程中,iview遇到的一些坑以及解决办法
写在开头:本次项目采用的是vue2.0+iview3.0,最近公司没啥事,来总结一下开发过程中遇到的问题. 1.Modal关闭问题 需求背景:modal框里面是个form表单,点击确定之后,先验证fo ...
- Eclipse 读取config目录下文件
最近在一个项目,在项目下新建了一个config配置文件夹,添加一个配置文件config.properties. 使用classpath:config.properties方式加载配置文件, 具体实现代 ...
- CSS视觉格式化模型
CSS视觉格式化模型(visual formatting model)是用来处理文档并将它显示在视觉媒体上的机制.这是CSS 2.1的一个基础概念.视觉格式化模型根据CSS盒模型为文档的每个元素生成0 ...
- startActivityForResult用法
使用场景:A界面(activity) 可跳转到一个(假设为 B)或多个子Activity,要求B处理完工作之后返回A 界面,或许同时返回一些数据交给A继续处理.如 由登陆界面A跳转到注册界面B,注册成 ...
- [笔记]《算法图解》第十章 K最近邻算法
K最近邻算法 简称KNN,计算与周边邻居的距离的算法,用于创建分类系统.机器学习等. 算法思路:首先特征化(量化) 然后在象限中选取目标点,然后通过目标点与其n个邻居的比较,得出目标的特征. 余弦相似 ...
- 目标检测算法SSD在window环境下GPU配置训练自己的数据集
由于最近想试一下牛掰的目标检测算法SSD.于是乎,自己做了几千张数据(实际只有几百张,利用数据扩充算法比如镜像,噪声,切割,旋转等扩充到了几千张,其实还是很不够).于是在网上找了相关的介绍,自己处理数 ...
- Java中转发与重定向的区别
转发与重定向的区别 转发是服务器行为,重定向是客户端行为 1.转发在服务器端完成的:重定向是在客户端完成的2.转发的速度快:重定向速度慢3.转发的是同一次请求:重定向是两次不同请求4.转发不会 ...
- [NOI2010][bzoj2005] 能量采集 [欧拉函数+分块前缀和优化]
题面: 传送门 思路: 稍微转化一下,可以发现,每个植物到原点连线上植物的数量,等于gcd(x,y)-1,其中xy是植物的横纵坐标 那么我们实际上就是要求2*sigma(gcd(x,y))-n*m了 ...
- include和require的区别误区
面试时总会被问到include和require的区别,回答的时候一般也是有以下几种区别: 1.include引入文件的时候,如果碰到错误,会给出警告,并继续运行下边的代码. require引入文件的时 ...