http://blog.csdn.net/carolzhang8406/article/details/6366043
先转一篇好文章:
http://terrylee.me/blog/post/2010/09/26/garbage-collection-and-performance-part2.aspx
本文是关于垃圾回收讲座的第二篇,第一篇为《垃圾回收系列(1):没有GC,世界将会怎样 》。 本文主要关注垃圾回收算法。垃圾回收机制,最早出现于世界上第二元老语言Lisp,Jean E. Sammet曾经说过,Lisp语言最长久的共享之一是一个非语言特征,即代表了系统自动处理内存的方法的术语极其技术——垃圾收集 (GC,Garbage Collection)。接下来我们介绍几种经典的垃圾回收算法,这些算法尽管出现于60、70年代,但是现在的CLR、JVM等上面的垃圾回收器,仍然 使用了它们。
引用计数算法
引用计数(Reference Counting)算法是每个对象计算指向它的指针的数量,当有一个指针指向自己时计数值加1;当删除一个指向自己的指针时,计数值减1,如果计数值减为 0,说明已经不存在指向该对象的指针了,所以它可以被安全的销毁了。可以很直观的用下面的图表示:
引用计数算法的优点在于内存管理的开销分布于整个应用程序运行期间,非常的“平滑”,无需挂起应用程序的运行来做垃圾回收;而它的另外一个优势在于 空间上的引用局部性比较好,当某个对象的引用计数值变为0时,系统无需访问位于堆中其他页面的单元,而后面我们将要看到的几种垃圾回收算法在回收前都回遍 历所有的存活单元,这可能会引起换页(Paging)操作;最后引用计数算法提供了一种类似于栈分配的方式,废弃即回收,后面我们将要看到的几种垃圾回收 算法在对象废弃后,都会存活一段时间,才会被回收。
引用计数算法有着诸多的优点,但它的缺点也是很明显的。首先能看到的一点是时间上的开销,每次在对象创建或者释放时,都要计算引用计数值,这会引起 一些额外的开销;第二是空间上的开销,由于每个对象要保持自己被引用的数量,必须付出额外的空间来存放引用计数值;引用计数算法最大的缺点就在于它无法处 理环形引用,如下图所示:
此处蓝色的这两个对象既不可达也无法回收,因为彼此之间互相引用,它们各自的计数值都不为0,这种情况对引用计数算法来说是无能为力的,而其他的垃圾回收算法却能很好的处理环形引用。
引用计数算法最著名的运用,莫过于微软的COM技术,大名鼎鼎的IUnknown接口:
interface IUnknown { virtual HRESULT _stdcall QueryInterface (const IID& iid, void * * ppv) = 0; virtual ULONG _stdcall AddRef() = 0; virtual ULONG _stdcall Release() = 0; }
其中的AddRef和Release就是用来让组件自己管理其生命周期,而客户程序只关心接口,而无须再去关心组件的生命周期,一个简单的使用示例如下:
int main() { IUnknown* pi = CreateInstance(); IX* pix = NULL; HRESULT hr = pi->QueryInterface(IID_IX, (void *)&pix); if (SUCCEEDED(hr)) { pix->DoSomething(); pix->Release(); } pi->Release(); }
上面的客户程序在CreateInstance中已经调用过AddRef,所以无需再次调用,而在使用完接口后调用Release,这样组件自己维护的计数值将会改变。下面代码给出一个简单的实现AddRef和Release示例:
ULONG _stdcall AddRef() { return ++ m_cRef; } ULONG _stdcall Release() { if (--m_cRef == 0) { delete this ; return 0; } return m_cRef; }
在编程语言Python中,使用也是引用计数算法,当对象的引用计数值为0时,将会调用__del__函数,至于为什么Python要选用引用计数 算法,据我看过的一篇文章里面说,由于Python作为脚本语言,经常要与C/C++这些语言交互,而使用引用计数算法可以避免改变对象在内存中的位置, 而Python为了解决环形引用问题,也引入gc模块,所以本质上Python的GC的方案是混合引用计数和跟踪(后面要讲的三个算法)两种垃圾回收机 制。
标记-清除算法
标记-清除(Mark-Sweep)算法依赖于对所有存活对象进行一次全局遍历来确定哪些对象可以回收,遍历的过程从根出发,找到所有可达对象,除 此之外,其它不可达的对象就是垃圾对象,可被回收。整个过程分为两个阶段:标记阶段找到所有存活对象;清除阶段清除所有垃圾对象。
标记阶段:
清除阶段:
相比较引用计数算法,标记-清除算法可以非常自然的处理环形引用问题,另外在创建对象和销毁对象时时少了操作引用计数值的开销。它的缺点在于标记- 清除算法是一种“停止-启动”算法,在垃圾回收器运行过程中,应用程序必须暂时停止,所以对于标记-清除算法的研究如何减少它的停顿时间,而分代式垃圾收 集器就是为了减少它的停顿时间,后面会说到。另外,标记-清除算法在标记阶段需要遍历所有的存活对象,会造成一定的开销,在清除阶段,清除垃圾对象后会造 成大量的内存碎片。
标记-缩并算法
标记-缩并算法是为了解决内存碎片问题而产生的一种算法。 它的整个过程可以描述为:标记所有的存活对象;通过重新调整存活对象位置来缩并对象图;更新指向被移动了位置的对象的指针。
标记阶段:
清除阶段:
缩并阶段:
标记-压缩算法最大的难点在于如何选择所使用的压缩算法,如果压缩算法选择不好,将会导致极大的程序性能问题,如导致Cache命中率低等。一般来说,根据压缩后对象的位置不同,压缩算法可以分为以下三种:
1. 任意:移动对象时不考虑它们原来的次序,也不考虑它们之间是否有互相引用的关系。
2. 线性:尽可能的将原来的对象和它所指向的对象放在相邻位置上,这样可以达到更好的空间局部性。
3. 滑动:将对象“滑动”到堆的一端,把存活对象之间的自由单元“挤出去”,从而维持了分配时的原始次序。
节点拷贝算法
节点拷贝算法是把整个堆分成两个半区(From,To), GC的过程其实就是把存活对象从一个半区From拷贝到另外一个半区To的过程,而在下一次回收时,两个半区再互换角色。在移动结束后,再更新对象的指针引用,GC开始前的情形:
GC结束后的情形:
节点拷贝算法由于在拷贝过程中,就可以进行内存整理,所以不会再有内存碎片的问题,同时也不需要再专门做一次内存压缩。,而它最大的缺点在于需要双倍的空间。
总结
本文总共介绍了四种经典的垃圾回收算法,其中后三种经常称之为跟踪垃圾回收,因为引用计数算法能够平滑的进行垃圾回收,而不会出现“停止”现象,经 常出现于一些实时系统中,但它无法解决环形问题;而基于跟踪的垃圾回收机制,在每一次垃圾回收过程中,要遍历或者复制所有的存活对象,这是一个非常耗时的 工作,一种好的解决方案就是对堆上的对象进行分区,对不同区域的对象使用不同的垃圾回收算法,分代式垃圾回收器正是其中一种,CLR和JVM中都采用了分 代式垃圾回收机制,但它们在处理上又有些不同,后面的文章再详细介绍这两种垃圾回收器的区别。
与垃圾回收相关的模块:gc
一些方法:
http://docs.python.org/library/gc.html
在 Python 中,为了解决内存泄漏问题,采用了对象引用计数,并基于引用计数实现自动垃圾回收。
因为 Python 有了自动垃圾回收功能,不少初学者就认为自己从此过上了好日子,不必再受内存泄漏的骚扰了。但如果查看一下 Python 文档对 __del__() 函数的描述,就知道好日子里也是有阴云的。下面摘抄一点文档内容:
Some common situations that may prevent the reference count of an object from going to zero include: circular references between objects (e.g., a doubly-linked list or a tree data structure with parent and child pointers); a reference to the object on the stack frame of a function that caught an exception (the traceback stored in sys.exc_traceback keeps the stack frame alive); or a reference to the object on the stack frame that raised an unhandled exception in interactive mode (the traceback stored in sys.last_traceback keeps the stack frame alive).
可见,有 __del__() 函数的对象间的循环引用是导致内存泄漏的主凶。特别说明:对没有 __del__() 函数的 Python 对象间的循环引用,是可以被自动垃圾回收掉的。
如何知道一个对象是否内存泄漏了呢?
方法一、当你认为一个对象应该被销毁时(即引用计数为 0),可以通过 sys.getrefcount(obj) 来获取对象的引用计数,并根据返回值是否为 0 来判断是否内存泄漏。如果返回的引用计数不为 0,说明在此刻对象 obj 是不能被垃圾回收器回收掉的。
方法二、也可以通过 Python 扩展模块 gc 来查看不能回收的对象的详细信息。
首先,来看一段正常的测试代码:
#--------------- code begin --------------
# -*- coding: utf-8 -*-
import gc
import sys
class CGcLeak(object):
def __init__(self):
self._text = '#'*10
def __del__(self):
pass
def make_circle_ref():
_gcleak = CGcLeak()
# _gcleak._self = _gcleak # test_code_1
print '_gcleak ref count0:%d' % sys.getrefcount(_gcleak)
del _gcleak
try:
print '_gcleak ref count1:%d' % sys.getrefcount(_gcleak)
except UnboundLocalError:
print '_gcleak is invalid!'
def test_gcleak():
# Enable automatic garbage collection.
gc.enable()
# Set the garbage collection debugging flags.
gc.set_debug(gc.DEBUG_COLLECTABLE | gc.DEBUG_UNCOLLECTABLE | /
gc.DEBUG_INSTANCES | gc.DEBUG_OBJECTS)
print 'begin leak test...'
make_circle_ref()
print 'begin collect...'
_unreachable = gc.collect()
print 'unreachable object num:%d' % _unreachable
print 'garbage object num:%d' % len(gc.garbage)
if __name__ == '__main__':
test_gcleak()
#--------------- code end ----------------
在 test_gcleak() 中,设置垃圾回收器调试标志后,再用 collect() 进行垃圾回收,最后打印出该次垃圾回收发现的不可达的垃圾对象数和整个解释器中的垃圾对象数。
gc.garbage 是一个 list 对象,列表项是垃圾收集器发现的不可达(即是垃圾对象)、但又不能释放(即不能回收)的对象。文档描述为:A list of objects which the collector found to be unreachable but could not be freed (uncollectable objects).
通常,gc.garbage 中的对象是引用环中的对象。因为 Python 不知道按照什么样的安全次序来调用环中对象的 __del__() 函数,导致对象始终存活在 gc.garbage 中,造成内存泄漏。如果知道一个安全的次序,那么就打破引用环,再执行 del gc.garbage[:] ,以清空垃圾对象列表。
上段代码输出为(#后字符串为笔者所加注释):
#-----------------------------------------
begin leak test...
# 变量 _gcleak 的引用计数为 2.
_gcleak ref count0:2
# _gcleak 变为不可达(unreachable)的非法变量.
_gcleak is invalid!
# 开始垃圾回收
begin collect...
# 本次垃圾回收发现的不可达的垃圾对象数为 0.
unreachable object num:0
# 整个解释器中的垃圾对象数为 0.
garbage object num:0
#-----------------------------------------
可见 _gcleak 对象的引用计数是正确的,也没有任何对象发生内存泄漏。
如果不注释掉 make_circle_ref() 中的 test_code_1 语句:
_gcleak._self = _gcleak
也就是让 _gcleak 形成一个自己对自己的循环引用。再运行上述代码,输出结果就变成:
#-----------------------------------------
begin leak test...
_gcleak ref count0:3
_gcleak is invalid!
begin collect...
# 发现可以回收的垃圾对象: 地址为 012AA090,类型为 CGcLeak.
gc: uncollectable <CGcLeak 012AA090>
gc: uncollectable <dict 012AC1E0>
unreachable object num:2
#!! 不能回收的垃圾对象数为 1,导致内存泄漏!
garbage object num:1
#-----------------------------------------
可见 <CGcLeak 012AA090> 对象发生了内存泄漏!!而多出的 dict 垃圾就是泄漏的 _gcleak 对象的字典,打印出字典信息为:
{'_self': <__main__.CGcLeak object at 0x012AA090>, '_text': '##########'}
相关推荐
Python垃圾回收机制是编程语言中一个重要的组成部分,它主要用于自动管理程序运行时的内存资源,确保内存的有效利用和及时释放。在Python中,垃圾回收机制是实现内存管理的关键工具,帮助程序员避免了手动管理内存...
"浅析Python垃圾回收机制" Python垃圾回收机制是指在Python程序执行过程中,动态申请内存空间,并在不再需要使用这些内存空间时释放它们,以避免内存泄漏。Python中的垃圾回收机制主要以引用计数为主,标记清除和分...
### 理解Python垃圾回收机制 #### 一、引言 Python作为一种广泛使用的高级编程语言,其自动化的内存管理机制极大地简化了开发者的负担。其中最重要的特性之一就是垃圾回收,它能自动检测并回收不再使用的内存空间。...
Python的垃圾回收机制是其内存管理的关键组成部分,它确保了程序在运行过程中有效且安全地管理内存。在Python中,由于一切都是对象,内存管理尤为重要,防止因无序分配和释放导致的内存泄漏或"out of memory"错误。...
Python垃圾回收机制是编程语言中用于自动管理内存的重要特性,它负责识别并释放不再使用的对象,从而避免内存泄漏。在Python中,有三种主要的垃圾回收实现方法:引用计数、标记清除和分代回收。 **引用计数**是最...
这篇文章主要介绍了python垃圾回收机制(GC)原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 今天想跟大家分享的是关于python的垃圾回收机制,虽然本人...
Python垃圾回收机制是Python编程语言中的一个重要特性,它自动管理程序的内存使用,避免程序员手动进行繁琐且可能出错的内存释放操作。Python中的垃圾回收(GC)主要由三种策略组成:引用计数、标记-清除和分代回收...
Python作为一门广泛使用的高级编程语言,其内置的垃圾回收机制对于管理内存至关重要。垃圾回收机制是指利用程序语言本身提供的工具来自动回收程序不再使用的内存空间,从而避免内存泄漏和其他内存相关的错误。在...
本文将深入探讨Python垃圾回收的原理、问题及其解决方案。 首先,理解为什么需要垃圾回收机制至关重要。在Python中,解释器在遇到变量定义时,会在内存中分配空间存储变量值。然而,内存资源有限,当变量不再使用时...
什么是垃圾回收机制? 首先,咱先来解释名词,垃圾回收是不是就是将没用的,废弃的东西回收起来。 在坐的各位都没有女朋友对吧,那难以想象你们的房间会是一个什么样子,可能会有很多垃圾,很凌乱,自己也不收拾。那...
"垃圾回收机制"这个概念在很多高级语言如Java或Python中是非常常见的一种自动内存管理方式,但在C++中,它并不是标准库的一部分。C++的内存管理主要依赖于程序员手动进行,通过new和delete操作符来分配和释放内存。...
垃圾回收机制 Python中的垃圾回收是以引用计数为主,分代收集为辅。引用计数的缺陷是循环引用的问题。 在Python中,如果一个对象的引用数为0,Python虚拟机就会回收这个对象的内存。 #encoding=utf-8 __author__ = ...
Python垃圾回收机制是编程语言中自动管理内存的一种方式,它主要负责识别并释放不再使用的对象,从而避免内存泄漏。在Python中,垃圾回收机制对于初学者来说是一个重要的概念,因为它确保了程序的稳定运行和资源的...