`
RednaxelaFX
  • 浏览: 3016694 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

垃圾回收与弱引用

阅读更多
在一个允许在堆上动态分配内存空间并且采取隐式内存释放的程序设计语言里,如何确保内存的正确释放不再是程序员的关注点,而由运行时环境来提供支持。无法被程序引用的在堆上已分配的内存空间成为垃圾(无用内存单元)。运行时环境要清除垃圾有两种方式:比较积极的方式,引用计数;与比较懒惰的方式,垃圾回收

引用计数方式会为每个已分配内存单元设置计数器,当计数器减少到0的时候就意味着该单元无法再被引用,于是立即执行释放内存的动作。垃圾回收方式的基本思想是mark-and-sweep(标记-清除),每隔一段时间或者在堆空间不足的时候才进行一次垃圾回收,每次垃圾回收先将所有堆上分配的内存单元标记为“不可到达”,然后从一组根引用开始扫描,把所有从根引用出发可以达到的单元标记为“可以到达”;然后把标记为“不可到达”的内存单元回收到可用的堆空间中。

这两种清除垃圾的方式的特点很不一样。
其中,引用计数方式有四个主要问题:
1、如果分配的内存单元本身比较小,则用于计数的计数器所占的空间就会变得明显(significant),而垃圾回收方式只需要为每个分配的内存单元设置一个比特位的标记;
2、维护计数器的状态需要消耗时间。每当一个指针或者引用被赋值的时候,计数器的状态都要跟着改变。像LISP这样的语言,几乎所有操作都涉及改变指针(因为要操纵链表),计数器的状态维护会占据整个程序执行时间中明显的部分。这并不像MSDN Channel 9上Stephan T. Lavavej对C++ TR1中的介绍中所说的“shared_ptr没有垃圾回收所带来的额外时间消耗”,引用计数只不过是把这种消耗平均的分摊到了程序运行的整个过程中而已;
3、计数器相关的代码可能会分布在运行时系统,甚至用户代码的各处,不便于代码的维护;
4、当存在循环引用时,内存的正确释放会比较复杂。当然并不是不可解决,下文会再提到。

标记-清除式的垃圾回收则有另外的一些问题:
1、标记-清除会不定时的产生运算资源消耗的高峰(spike)。在没有进行垃圾回收的时候,程序可以运行得比较顺畅,但在执行垃圾回收的时候一般需要把整个程序停下来并执行标记-清除的过程,除非使用并行回收机制。标记的过程可能很长并且很消耗资源,如果是实时系统则一般无法承受这种消耗高峰而宁可使用引用计数方式将消耗分摊到程序的整个运行过程上。分代式的垃圾回收在一定程度上缓解了这个问题,但并没有根除消耗高峰的问题;
2、当你最需要垃圾回收器工作的时候,它的运行效果却最差。最需要进行垃圾回收的时候显然是堆上的内存已经快分配尽了的时候,但此时已分配的内存单元很多,需要使用大量时间来做标记,但实际能释放的内存单元却未必很多。
3、标记-清除算法有两种实现思路,一是“保守式”(conservative),二是“准确式”(exact)。保守式不需要知道内存的具体布局形式,会把栈上和全局区上所有“看起来像指针”的数值看作指针并纳入标记计算中;准确式则要求运行时系统清楚的了解内存布局形式,能够分辨哪些数据是指针哪些不是,并且只将指针纳入标记计算中。前者未必能保证内存的准确释放(但能够保证正在被引用的内存不被释放),后者则相对需要消耗更多的内存和更多的时间。
有名的Boehm GC就是保守式的代表。去年开源了的Adobe Virtual Machine 2(AVM2,又称Tamarin)中的MMgc也是保守式的。未换用Tamarin之前的SpiderMonkey则使用了准确式的垃圾回收。

=========================================================================================

当代许多程序设计语言的运行时系统都采用了隐式内存释放的设计,并出于各自的设计目标选用了不同的清除垃圾的方法。
在许多采用引用计数方式实现垃圾清除的系统中都有所谓的弱引用,例如说Squirrel,为的是解决循环引用的问题。让我们来看看Squirrel 2.2参考手册里的一段描述:
引用
Weak References

The weak references allows the programmers to create references to objects without influencing the lifetime of the object itself. In squirrel Weak references are first-class objects created through the built-in method obj.weakref(). All types except null implement the weakref() method; however in bools, integers and float the method simply returns the object itself(this because this types are always passed by value). When a weak references is assigned to a container (table slot, array, class or instance) is treated differently than other objects; When a container slot that hold a weak reference is fetched, it always returns the value pointed by the weak reference instead of the weak reference object. This allow the programmer to ignore the fact that the value handled is weak. When the object pointed by weak reference is destroyed, the weak reference is automatically set to null.

换句话说,如果我们预先知道可能出现循环引用状况,而其中一些引用并不需要那么“强”,那么我们就可以使用弱引用来消除循环引用的问题。在这个语境下,弱引用就是不会影响计数器状态的引用。这意味着即使我们拥有对某个对象的弱引用也不会阻止它被清除;也就是说,我们并不能知道手上的弱引用是否指向一个有效的对象。一般弱引用的实现都会保证当某个弱引用指向的不是有效对象时它会被设置为空值,例如null、nil、Nothing之类。

有人为Visual Basic 5/6也提出了弱引用的实现方法:Avoiding Circular References: WeakReference in VB-Classic

微软的Component Object Model(COM)可以说是应用的最广泛的采用引用计数式垃圾清除的系统吧,但它的IUnknown.AddRef和IUnknown.Release方法没少给程序员带来头疼,而且也没提供解决统一的循环引用检测机制。即便如此现在还是有很多大型系统运行于其上,而且看起来好好的(摇头
不,不都是好好的。想想IE的内存泄漏问题吧。在IE里用JScript想要造成内存泄漏还是挺简单的,不知道怎么做的话搜一下吧。^ ^

吉里吉里2也采用了引用计数方式的垃圾清除,但为了保证垃圾的清除,特别是引用的循环和程序结束时的资源释放,做了许多特别措施。这个另外在找时间写。

=========================================================================================

如上文所述,在引用计数环境中使用弱引用主要是为了解决循环引用带来的问题。而标记-清除方式的垃圾回收并不会受循环引用的影响:假如一组对象相互存在引用,而从根引用组出发已经无法达到它们之中的任何一个,则它们仍然会被回收。
但是许多采取标记-清除方式垃圾回收的环境也提供了弱引用。为什么呢?

首先想到的,弱引用肯定是为了解决问题而存在的;也就是说垃圾回收还是有问题,无法根除内存泄漏的问题。

在这种环境下的内存泄漏经常是人为失误造成的:无意的长时间持有了对已经不需要的对象的引用。这种引用经常存在于生命期特别长的对象中,例如一些全局对象中;Java和C#等语言虽然不允许在类之外定义全局数据,但类变量(而不是成员变量)或者诸如singleton等的特例的表现与C/C++中的全局变量并没有什么区别。

为了解决这样的问题,像Java和.NET Framework这样的平台也提供了弱引用机制。与引用计数环境一样,弱引用也不影响判定某个内存单元是否为垃圾的标记计算。

在Java中有三种“弱”引用:java.lang.ref.WeakReference<T>java.lang.ref.SoftReference<T>java.lang.ref.PhantomReference<T>
.NET Framework中也有System.WeakReference

下面几篇文章对它们分别做了介绍:
Java theory and practice: Plugging memory leaks with weak references
Understanding Weak References
C# WeakReference Example

=========================================================================================

参考

·Concepts of Programming Languages, Seventh Edition, Heap Management, Pages 301-305, by Robert W. Sebesta。

P.S. 上文把"reference counting"与"garbage collection"(GC)放在了同一层次来讨论,但GC的定义在许多资料中都不一样。我个人的看法是"reference counting"只是与"mark-and-sweep"一样,属于GC的一种策略;GC这个概念应该比reference counting和mark-and-sweep的层次更抽象才对。不过由于本文参考的资料里是按照"reference counting"和"garbage collection"来区分的,这里也就沿用了。
分享到:
评论
1 楼 lwwin 2008-03-22  
最近单位里面用到的GC常常发生奇怪的问题,调试的时间也费了不少^o^

另外偶不太会写技术性的文章的拉^o^,大部分都写生活的东西,=-=~

相关推荐

    弱lru高速缓存:使用LRU和弱引用以与垃圾回收协调工作的方式缓存数据的缓存

    在典型的GC虚拟机中,对象在不再被(强烈)引用之后很长一段时间内可能会继续存在于内存中,但是使用弱引用缓存,我们允许GC收集此类数据,但是缓存可以返回此数据直到将其垃圾回收为止,以确保更有效地使用内存。...

    java弱引用

    java 弱引用代码以及分析,详细讲解弱引用与强引用在垃圾回收时产生的区别

    flex垃圾回收机制是什么原理

    这很好理解,每个 Flex 应用总有一个 Application 的入口被称为根节点(Root),垃圾收集器从根节点开始遍历每个对象,对可达对象标记为“有效”(有一种例外就是弱引用,后面的章节详谈)。而在这棵树之外的孤岛...

    理解Java中的弱引用(Weak Reference)

    本篇文章尝试从What、Why...  弱引用对象的存在不会阻止它所指向的对象变被垃圾回收器回收。弱引用常见的用途是实现规范映射(canonicalizing mappings,比如哈希表)。  假设垃圾收集器在某个时间点决定一个对象是弱

    解析Android开发优化之:软引用与弱引用的应用

    如果一个对象只具有弱引用,那么在垃圾回收器线程扫描的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现...

    Java弱引用与WeakHashMap

     《Java 理论与实践: 用弱引用堵住内存泄漏》一文也指出了使用全局的Map作为缓存容器时发生的内存泄露问题,介绍了如何使用hprof工具来找出内存泄露,并分析了如何使用弱引用来防止内存泄露,还分析了

    Java 理论与实践:用弱引用堵住内存泄漏

    本月,负责保障应用程序健康的工程师 Brian Goetz 探讨了无意识的对象保留的常见原因,并展示了如何用弱引用堵住泄漏。  要让垃圾收集(GC)回收程序不再使用的对象,对象的逻辑 生命周期(应用程序使用它的时间...

    智能指针与弱引用详解

    在android 中可以广泛看到的template&lt;typename&gt; class Sp 句柄类实际上是android 为实现垃圾回收机制的智能指针。智能指针是c++ 中的一个概念,因为c++ 本身不具备垃圾回收机制,而且指针也不具备构造函数和析构函数...

    java四大引用

    有利用垃圾回收 强引用 使用最广泛的 Object o = new Object(); ​ 只要某个对象有强引用与之关联,这个对象就永远不会被回收,即使内存不足,JVM也只会抛出OOM也不回去回收。 ​ 直到强引用和对象之间的关联被中断...

    【JVM和性能优化】2.垃圾回收器和内存分配策略

    文章目录内存回收引用计数法可达性分析浅谈引用强引用软引用弱引用虚引用方法区GC 算法标记-清除算法(Mark-Sweep)复制算法(Copying)标记-整理算法(Mark-Compact)GC算法综合用年轻代老年代永久代枚举根节点安全...

    Flex内存管理及相关内容

    flex内存管理机制 垃圾回收 弱引用 FLEX内存释放优化原则 1. 被删除对象在外部的所有引用一定要被删除干净才能被系统当成垃圾回收处理掉; 2. 父对象内部的子对象被外部其他对象引用了,会导致此子对象不会被删除,...

    Java虚拟机(四)——Java引用对象4种类型

    引用类型是一个对象类型,...只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。如 果想中断强引用与对象之间的联系,可以显示的将强引用赋值为n

    理解Java中的弱引用

    ”,如果面试者这样说,“嗯,是不是垃圾回收有关的?”,我会基本满意了,我并不期待回答是一篇诘究本末的论文描述。  然而事与愿违,我很吃惊的发现,在将近20多个有着平均5年开发经验和高学历背景的应聘者中,...

    Java对象的强、软、弱和虚引用1

    3.2 如果使用软引用SoftReference的特点是它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收 3.

    Python3标准库:weakref对象的非永久引用.docx

    正常的引用会增加对象的引用数,并避免它被垃圾回收。但结果并不总是如期望中的那样,比如有时可能会出现一个循环引用,或者有时需要内存时可能要删除对象的缓存。弱引用(weak reference)是一个不能避免对象被自动...

    Android避免内存溢出(Out of Memory)方法汇总

    在处理内存引用之前,我们先来复习下什么是强引用、软引用、弱引用、虚引用 强引用:强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。 当内存空间不足,Java虚拟机宁愿抛出...

    《垃圾收集》(Garbage Collection)扫描版[PDF]——part2

    书围绕着动态内存自动回收的话题,介绍了垃圾收集机制,详细分析了各种算法和相关技术。  本书共12章。第1章首先介绍计算机存储器管理的演化和自动内存回收的需求,并引入了本书所使用的术语和记法。第2章介绍了3...

    proposal-weakrefs:弱引用

    V8-运送 Spidermonkey-发行 (当前Beta) JavaScriptCore-engine262-,现在全部着陆XS-介绍WeakRef提案包含两个主要的新功能: 使用WeakRef类创建对对象的弱引用在对象被垃圾回收之后,使用FinalizationRegistry类...

    python如何在循环引用中管理内存

    python中通过引用计数来回收垃圾对象,在某些环形数据结构(树,图……),存在对象间的循环引用,... 通过weakref,进行弱引用,当del时候,不再引用,在引用方添加weakref.ref(引用obj); 使用引用的时候,需要用到

Global site tag (gtag.js) - Google Analytics