小议垃圾回收算法

来源:互联网 发布:b站弹幕源码 编辑:程序博客网 时间:2024/05/20 04:11


原文链接:http://blog.chinaunix.net/uid-26611383-id-3538420.html



今天接到一个电话面试,幼小的心灵又被无情的重创了一次。电话那头的面试官很客气的说了下他是大众点评的,然后就开始了电话面试。
心理嘀咕,不用这么高效率吧,给哥点心理准备啊,妹的,^_^.
首先问了一下我基础知识,下面是部分对话内容
面试官:java怎么样
我:不会
面试官:那你c/c++是不是比较熟
我:还行
说完似乎有点心理没底了,不过话都说出去了,只好硬着头皮来了,男人要浮点责任嘛,再说对c/c++还是有点靠谱的
面试官:你实现过垃圾回收机制的算法吗
我的感慨:尼玛,哥只听说JVM有自己的垃圾回收机制,垃圾回收还设计到算法啊难道,顿时慌了,刚还说c/c++不错呢,这人丢大了。
    深吸了一口气,
脑袋里冒出来个想法,忽然想到python里面的对象都是采用引用计数的,不知道当时看到哪一本书上说的,然后我
    就告诉了他引用计数的算法

我:我只知道引用计数法,每个对象都维护该对象的引用的技术,每增加该对象的引用,计数加一,反之减一,直到引用计数为0,
    此时释放该对象所占有的内存空间
我的感慨:此时我对我的答案还不确定,这跟c++也没什么必然关系啊,不管了,反正也就知道这点了
    面试官:那你知道这种算法最大的缺点在哪里吗?
我的感慨:尼玛,哥好不容易废了九牛二虎之力才想到这么点,不过没想到居然被我误打误撞说了那么一点,心理一放松,就随便说了
    我:维护引用计数需要一定的内存空间,而且每次创建,销毁一个对象都要计算该对象的引用计数,开销会比较大
面试官:这个不是最重要的缺陷
    我:不好意思,我实在想不出来了
面试官:好的,那聊其他方面的吧.......
...
...
...

电话面试之后觉得自己太差劲了,又恶补了一下之前没回答上来的问题,现在跟大家分享一下垃圾回收机制的一些算法和其优缺点

1. 引用计数(Reference Counting)

   我在面试过程中唯一说出的一种算法就是这个,当时冒出这个念头还是出于我知道Python中的垃圾回收机制是基于引用计数算法的
引用技术算法的原理:
    引用计数,顾名思义,就是每个对象上有个计数器,当添加了一个对它的引用时它的计数器就会加1,当不再使用这个引用时它的计数器就会递减1。
当计数器为0的时候则认为该对象是垃圾,可以被回收了

优点:
    该算法可以平滑的进行垃圾回收,这里的”平滑“的意思是可以在程序运行的过程中不断的进行垃圾回收,而不用使程序挂起,然后再专门进行垃圾
回收。后面有其它的算法就是必须使得程序挂起才能进行垃圾回收
缺陷:
    之前谈到过,该算法的缺点就是要为每个生成的对象维护一个引用计数,不断的更新该计数,这样势必在成时间和空间上的部分开销。这也是我面试过程中
回答的一点。不过该算法还有先天性的缺陷,也就是该算法不能解决”循环引用“的问题。所谓循环引用就是两个对象之间互相引用,那么这两个对象的引用计数就不可能为0,
也就不能被回收。这跟进程/线程之间的死锁有点类似。可能举个例子更容易理解,下面的一段话是在网上摘抄的:
比如现在A对象引用B对象,B对象的计数器加1,然后B引用C,C的计数加1,后来C又引用B,B的计数加1得到2。假如现在A不再引用B了,B的计数器成为1。
而由于B、C互相引用,形成一个孤岛,但是计数器又没有变成0,又无法回收
。(引用自:http://www.cnblogs.com/yuyijq/archive/2011/05/28/2060733.html)

下面的几种算法都属于跟踪算法,为什么叫跟踪算法,看下去就知道了

2.标记-清扫(Mark-Sweep)

    引用技术算法不能解决”循环引用“的问题,那么我们再来看看Mark-sweep算法
Mark-Sweep算法的原理:
    该算法分为两个阶段,第一阶段为标记。该算法遍历所有的对象,然后标记出活动的对象,也就是引用技术算法中的那些引用的计数大于0的对象。
第二阶段为清楚,既然标记出了活动的对象,那么就可以清除掉剩余的非活动对象的内存空间了。这是我的理解。下面一段话也是引用自上面的链接,觉得
说得比较好

首先垃圾收集器会确定一些根(root):比如线程当前正在执行的方法的局部变量,方法参数,以及所在类的实例变量及所有静态变量。然后垃圾收集器会从这些根对象出发查找他们所有的引用,然后在被引用的对象上作标记(mark),当垃圾收集器遇上一个已经被标记的对象后就不再往前走了(避免循环标记,造成死循环)。当标记阶段结束后,所有被标记的对象就称为可达对象(Reachable Object)或活对象(Live Object),而所有没有被标记的对象则被认为是垃圾,可以被回收。这个阶段进行完后就进入了清扫阶段(Sweep Phase)。在清扫阶段,垃圾收集器会从堆的底部开始扫描,然后将没有做标记的对象所占的内存全部回收,比如将这些垃圾添加到一个空闲空间链表。http://www.cnblogs.com/yuyijq/archive/2011/05/28/2060733.html
   
    该算法的优点:
    解决了循环引用的问题,节省了引用计数带来的开销
    缺陷:
    该算法带来了很大的内存碎片,为什么?试想一下,刚开始的内存很可能是连续分配的,即刚开始的时候,新创建的对象所占内存空间是连续的,但是随着程序的运行,
其中的某些对象生命周期结束了,没有被标记为活动对象,那么就要被垃圾回收器回收。这样就造成了大量的内存碎片,在以后重新申请连续的内存时可能会失败。

3.拷贝节点

    该算法的原理很简单,不过简单的东西往往是最有效的,很佩服想出这种算法的大神,膜拜中.

    该算法的原理是将整个堆内存分为两个完全相等的两部分,是容量相等的两部分。为什么是堆内存?这个大家应该都很清楚,因为动态申请的内存都在堆中,栈里的对象不用我们
操心,系统会帮我们释放的。首先我们假设真个堆内存分为第一部分和第二部分,程序的所有的动态分配的对象都在第一部分堆内存中,当第一部分内存空间占满时会触发垃圾回收机制
,此时利用上面的标记-清除算法的第一步,标记出所有的活动对象,然后将这些活动对象转移到第二部分内存空间。此时第一部分堆内存里的都是非活动对象,可以回收这些对象了。等到第二部分堆内存满了,再将活动对象转移到第一部分堆内存,这样不断循环下去,直到整个程序结束,这就是该算法的核心思想,是不是觉得很奇妙,再次膜拜该算法的发明者。

优点:
    该算法实现简单而且很好的解决了Mark-Sweep算法中的内存碎片问题,因为整个堆内存就分为两部分,程序的所有活动对象就是在这两个完整的堆内存中不断转移。
缺点:
    没有最好,只有更好,该算法仍然存在缺陷,很多人认为这个缺陷还很大,既然程序将堆内存非为了两个相等的部分,程序只运行在其中一部分的堆内存中,那么总是存在着一部分堆内势必空闲,这样的代价就是减少了内存空间,而且对内存的利用率减少了一半,代价确实很大。

本文只是介绍了三种最基本最经典的几种垃圾回收算法,其它的垃圾回收算法笔者还没有了解,以后有时间再补充。





0 0
原创粉丝点击