`
RednaxelaFX
  • 浏览: 3048264 次
  • 性别: 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^,大部分都写生活的东西,=-=~

相关推荐

    垃圾回收技术示例 垃圾回收技术示例

    3. 使用弱引用:弱引用不会阻止对象被垃圾回收,可以在需要时检查对象是否还存活。 4. 调整垃圾回收参数:每个语言都有相应的参数调整GC的行为,如新生代和老年代的比例,以适应不同场景的需求。 通过理解并熟练...

    C++引用计数设计与分析(解决垃圾回收问题).docx

    这就是为什么在C++中,智能指针如`std::shared_ptr`使用弱引用(`std::weak_ptr`)来打破这种循环引用,以实现更可靠的内存管理。 总的来说,C++的引用计数提供了一种管理动态内存的有效方式,但需要注意循环引用的...

    Java垃圾回收详解

    - **弱引用(Weak Reference)**:仅当GC进行时,无论系统内存是否充足,都会回收弱引用的对象。 - **虚引用(Phantom Reference)**:无法通过虚引用获取到对象实例,其主要用途是跟踪对象的垃圾回收过程。 每种...

    java垃圾回收(gc)机制详解.pdf

    在JDK1.2之后,Java对引用的概念进行了扩展,提出了四种引用状态:强引用、软引用、弱引用、虚引用,这四种引用类型对对象的垃圾回收有不同的影响。 1. 强引用 强引用是最传统的引用,类似于"Object obj = new ...

    灵巧指针与垃圾回收

    【标题】:“灵巧指针与垃圾回收” 在编程领域,尤其是在C++中,灵巧指针(Smart Pointer)和垃圾回收(Garbage Collection)是两个重要的概念,它们主要用于管理和释放内存,以防止内存泄漏。灵巧指针是C++标准库...

    java C#垃圾回收算法分析

    例如,通过减少对象创建,避免大量短生命周期对象产生,或者使用弱引用和软引用来控制对象的生命周期。同时,理解GC的工作原理有助于在遇到性能问题时,能够有效地定位和解决问题。 总的来说,垃圾回收是现代编程...

    .NETFramework垃圾回收3.5Version.pdf

    - 弱引用允许程序保持对对象的引用,但不会阻止垃圾回收器回收该对象。这意味着,即使有弱引用指向对象,当对象不再被其他强引用引用时,垃圾回收器仍可回收它。 3. **被动回收(Passive Collection)**: - 被动...

    JavaScript垃圾回收

    6. **弱引用(Weak Reference)**:弱引用是一种特殊的引用,不会阻止垃圾回收。当对象只通过弱引用来访问时,即使仍有其他强引用,垃圾回收器也会回收该对象,解决了循环引用问题。 7. **内存泄漏**:虽然...

    JVM垃圾回收机制

    垃圾回收机制中还涉及了对象的引用类型,包括强引用、软引用、弱引用和虚引用。强引用是常见的引用,只要对象存在强引用,垃圾回收器不会回收它。软引用用于描述一些还有用但非必须的对象,在内存不足时会被回收。弱...

    Flex垃圾回收机制

    - 使用弱引用:弱引用不会增加对象的引用计数,即使有弱引用指向的对象,当其所有强引用都被解除后,仍然会被垃圾回收器回收。 - 及时解除事件监听器:在不再需要事件监听时,应及时调用`removeEventListener`方法...

    java弱引用

    Java 弱引用是一种特殊类型的引用,它允许垃圾回收器在某些情况下回收对象,而不是像强引用那样坚持对象的存在。弱引用是 Java.lang.ref.WeakReference 类的实例,它们提供了一种方式来引用对象,而不阻止垃圾回收器...

    搞定面试官:咱们从头到尾再说一次 Java 垃圾回收(csdn)————程序.pdf

    弱引用比软引用更弱,一旦进行垃圾回收,无论内存状况如何,弱引用对象都会被清除。虚引用,又称幽灵或幻影引用,不能直接访问对象,仅在对象被垃圾回收时收到通知。 垃圾回收算法主要包括以下几种: 1. **标记...

    java垃圾回收及内存泄漏.pptx

    - 使用弱引用、软引用或虚引用来替代强引用。 - 避免使用静态集合类来存储对象。 - 正确注销监听器。 - 合理设计缓存策略。 #### 四、总结 Java垃圾回收机制是Java语言的一个重要特性,有效地解决了内存管理...

    对象引用、可变性和垃圾回收1

    弱引用是一种特殊类型的引用,它不会增加对象的引用计数,因此即使有弱引用存在,当其他所有强引用消失后,对象仍会被垃圾回收。弱引用对象的生命周期不由弱引用本身控制,而是由其所指对象的生命周期决定。使用弱...

    Java垃圾回收与回收机制.doc

    Java垃圾回收机制是Java虚拟机(JVM)内存管理的关键...通过明智地设计对象生命周期、使用弱引用和软引用等技术,可以进一步优化垃圾回收的效果。同时,理解和监控JVM的垃圾回收行为对于诊断和解决性能问题至关重要。

    强引用、软引用、弱引用、虚引用1

    一旦创建了弱引用,那么无论系统内存是否充足,只要进行垃圾回收,就会回收弱引用指向的对象。弱引用通常用于避免内存泄漏,特别是在那些对象不再需要,但因为其他原因(如静态集合)而无法立即回收的情况下。弱引用...

    Java基础复习笔记02对象状态、引用种类、垃圾回收形式

    在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间是否足够,都会回收它的内存。 4. **虚引用**:也称为幽灵引用或幻影引用,是最弱的一种引用关系。一个对象是否...

    Android中的软引用和弱引用

    接着,弱引用比软引用的级别更低,它关联的对象会在下一次垃圾收集发生时立即被回收,无论系统当前内存状况如何。弱引用通常用于那些可有可无的对象,例如临时数据或辅助对象。由于弱引用不会阻止垃圾收集,所以使用...

Global site tag (gtag.js) - Google Analytics