您的位置:首页 > 其它

垃圾回收算法

2015-10-23 14:37 239 查看
引用计数(Reference Counting)

比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。
缺点:此算法最致命的是无法处理循环引用的问题。当某些对象相互引用,同时这些对象构成的整体又不被其他对象引用时,本该回收这些内存,但是因为计数为1,不会被回收。

标记-清除(Mark-Sweep)

几个基本概念:

首先是mutator和collector,这两个名词经常在垃圾收集算法中出现,collector指的就是垃圾收集器,而mutator是指除 了垃圾收集器之外的部分,比如说我们应用程序本身。mutator的职责一般是NEW(分配内存),READ(从内存中读取内容),WRITE(将内容写 入内存),而collector则就是回收不再使用的内存来供mutator进行NEW操作的使用。
第二个基本概念是关于mutator roots(mutator根对象),mutator根对象一般指的是分配在堆内存之外,可以直接被mutator直接访问到的对象,一般是指静态/全局 变量以及Thread-Local变量(在Java中,存储在java.lang.ThreadLocal中的变量和分配在栈上的变量 - 方法内部的临时变量等都属于此类).
第三个基本概念是关于可达对象的定义,从mutator根对象开始进行遍历,可以被访问到的对象都称为是可达对象。这些对象也是mutator(你的应用程序)正在使用的对象。

此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。解决了循环引用的问题。
与引用计数算法不同的是,标记-清除算法不需要运行环境监测每一次内存分配和指针操作,而只要在“标记”阶段中跟踪每一个指针变量的指向。
缺点:此算法需要暂停整个应用,同时,会产生内存碎片。



从上图我们可以看到,在Mark阶段,从根对象1可以访问到B对象,从B对象又可以访问到E对象,所以B,E对象都是可达的。同理,F,G,J,K也都是可
达对象。到了Sweep阶段,所有非可达对象都会被collector回收。

由new操作,触发回收机制。
New():
ref <- allocate()  //分配新的内存到ref指针
if ref == null
collect()  //内存不足,则触发垃圾收集
ref <- allocate()
if ref == null
throw "Out of Memory"   //垃圾收集后仍然内存不足,则抛出Out of Memory错误
return ref

atomic collect():
markFromRoots()
sweep(HeapStart,HeapEnd)


Mark算法
markFromRoots():
worklist <- empty
for each fld in Roots  //遍历所有mutator根对象
ref <- *fld
if ref != null && isNotMarked(ref)  //如果它是可达的而且没有被标记的,直接标记该对象并将其加到worklist中
setMarked(ref)
add(worklist,ref)
mark()
mark():
while not isEmpty(worklist)
ref <- remove(worklist)  //将worklist的最后一个元素弹出,赋值给ref
for each fld in Pointers(ref)
//遍历ref对象的所有指针域,如果其指针域(child)是可达的,直接标记其为可达对象并且将其加入worklist中
//通过这样的方式来实现深度遍历,直到将该对象下面所有可以访问到的对象都标记为可达对象。
child <- *fld
if child != null && isNotMarked(child)
setMarked(child)
add(worklist,child)


Sweep算法
sweep(start,end):
scan <- start
while scan < end
if isMarked(scan)
setUnMarked(scan)
else
free(scan)
scan <- nextObject(scan)


复制(Copying)

为了解决Mark-Sweep算法缺陷,Copying算法产生。
此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。
算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。
缺点:就是需要两倍内存空间。

标记-整理(Mark-Compact)

此算法结合了“标记-清除”和“复制”两个算法的优点。
也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,
按顺序排放。
此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。

分代收集(Generational Collection)

分代收集是根据对象的存活时间把内存分为新生代和老年代,根据个代对象的存活特点,每个代采用不同的垃圾回收算法。
新生代采用标记—复制算法,老年代采用标记—整理算法。
根据内存中对象的存活周期不同,将内存划分为几块,java的虚拟机中一般把内存划分为新生代和年老代,当新创建对象时一般在新生代中分配内存空间,当新 生代垃圾收集器回收几次之后仍然存活的对象会被移动到年老代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在年老代中创建。

现在的Java虚拟机就联合使用了分代复制、标记-清除和标记-整理算法。java虚拟机垃圾收集器关注的内存结构如下:



堆内存被分成新生代和年老代两个部分,整个堆内存使用分代复制垃圾收集算法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: