在列表,元组,实例,类,字典和函数中存在循环引用问题。有 del 方法的实例会以健全的方式被处理。给新类型添加GC支持是很容易的。支持GC的Python与常规的Python是二进制兼容的。

分代式回收能运行工作(目前是三个分代)。由 pybench 实测的结果是大约有百分之四的开销。实际上所有的扩展模块都应该依然如故地正常工作(我不得不修改了标准发行版中的 new 和 cPickle 模块)。一个叫做 gc 的新模块马上就可以用来调试回收器和设置调试选项。

回收器应该是跨平台可移植的。Python 的补丁版本通过了所有的回归测试并且跑 Grail、Idle 和 Sketch 的时候没有任何问题。

自 Python 2.0 和之后的版本,可移植的垃圾回收机制已经包括在其中了。垃圾回收默认是开启的。请高兴些吧!

为什么我们需要垃圾回收?

目前版本的 Python 采用引用计数的方式来管理分配的内存。Python 的每个对象都有一个引用计数,这个引用计数表明了有多少对象在指向它。当这个引用计数为 0 时,该对象就释放了。引用计数对于多数程序都工作地很好。然而,引用计数有一个本质上的缺陷,是由于循环引用引起的。循环引用最简单的例子就是一个引用自身的对象。比如:

>>> l = []
>>> l.append(l)
>>> del l

这个创建的列表的引用计数现在是 1。然而,因为它从 Python 内部已经无法访问,并且可能没法再被用到了,它应该被当作垃圾。在目前版本的 Python 中,这个列表永远不会被释放。

一般情况下循环引用不是一个好的编程实践,并且几乎总该被避免。然而,有时候很难避免制造循环引用,要么则是程序员甚至没有察觉到循环引用的问题。对于长期运行的程序,比如服务器,这个问题特别令人烦恼。人们可不想他们的服务器因为循环引用无法释放访问不到的对象而耗尽内存。对于大型程序,很难发现循环引用是怎么创造出来的。

“传统的”垃圾回收是怎样的?

传统的垃圾回收(比如标记-清除法或者停止-拷贝法)通常工作如下:

找到系统的根对象。根对象就像是全局的环境(比如 Python 中的 main 模块)和堆栈上的对象。

从这些对象搜索所有的可以访问的对象。这些对象都是“活跃”的。

释放其他所有对象。

不幸的是这个方法不能用于当前版本的 Python。由于扩展模块的工作方式,Python 不能完全地确定根对象集合。如果根对象集合没法被准确地确定,我们就有释放仍然被引用的对象的风险。即使用其他方式设计扩展模块,也没有可移植的方式来找到当前 C 堆栈上的对象。而且,引用计数提供了一些 Python 程序员已然期待的有关局部性内存引用和终结语义的好处。最好是我们能够找到一个即能使用引用计数,又能够释放循环引用的的办法。

这个方法如何工作?

从概念上讲,这个方法与传统垃圾回收机制相反。这个方法试图去找到所有的不可访问对象,而不是去找所有的可访问对象。这样更加安全,因为如果这个算法失败了,起码不会比不进行垃圾回收还要糟(不考虑我们浪费掉的时间和空间)。

因为我们仍然在用引用计数,垃圾回收器只需要找到循环引用。引用计数会处理其他类型垃圾。首先我们观察到循环引用只能被容器对象创造。容器对象是可以包含其他对象的引用的对象。在Python中,列表、字典、实例、类和元祖都是容器对象的例子。整数和字符串不是容器。通过这个发现,我们意识到非容器对象可以被垃圾回收忽略。这是一个有用的优化因为整数和字符串这样的应该比较轻快。

现在我们的想法是记录所有的容器对象。有几种方法可以做到,然而最好的一种办法是利用双向链表,链表中的对象结构中包含指针字段。这样就可以使对象从集合中快速插入删除,而且不需要额外内存空间分配。当一个容器被创建,它就插入这个集合,被删除时,就从集合中去除。

既然我们能够得到所有的容器对象,我们怎么找到循环引用呢?首先我们往容器对象中添加两个指针外的另一个字段。我们命名这个字段 gc_refs。通过以下几步我们可以找到循环引用:

对每个容器对象,设 gc_refs 的值为对象的引用计数。

对每个容器对象,找到它引用的其他容器对象并把它们的 gc_refs 值减一。

所有的 gc_refs 大于 1 的容器对象是被容器对象集合外的对象所引用的。我们不能释放这些对象,所以我们把这些对象放到另一个集合。

被移走的对象所引用的对象也不能被释放。我们把它们和它们能访问到的对象都从目前集合移走。

在目前集合中的剩下的对象是仅被该集合中对象引用的(也就是说,他们无法被 Python 取到,也就是垃圾)。我们现在可以去释放这些对象。

Finalizer的问题

我们的宏伟计划还有一个问题,就是使用 finalizer 的问题。Finalizer 就是在 Python 中实例的__del__方法。使用引用计数时,Finalizer 工作地不错。当一个对象的引用计数降到 0 的时候,Finalizer 就在对象被释放前调用了。对程序员来说这是直接明了且容易理解的。

垃圾回收的时候,调用 finalizer 就成了一个麻烦的问题,尤其是面对循环引用的问题时。如果在循环引用中的两个对象都有 finalizer,该怎么做?先调用哪个?在调用第一个 finalizer 之后,这个对象无法被释放因为第二个 finalizer 还能取到它。

因为这个问题没有好的解决办法,被有 finalizer 的对象引用的循环是无法释放的。相反的,这些对象被加进一个全局的无法回收垃圾列表中。程序应该总是可以重新编写来避免这个问题。作为最后的手段,程序可以读取这个全局列表并以一种对于当前应用有意义的方式释放这些引用循环。

代价是什么?

就像有些人说的,天底下没有免费的午餐。然而,这种垃圾回收形式是相当廉价的。最大的代价之一是每各容器对象额外需要的三个字的内存空间。还有维护容器集合的开销。对当前版本的垃圾收集器来说,基于 pybench 这个开销大概是速度下降百分之四。

垃圾回收器目前记录对象的三代信息。通过调整参数,垃圾回收花费的时间可以想多小就多小。对一些应用来说,关掉自动垃圾回收并在运行时显式调用也许是有意义的。然而,以默认的垃圾回收参数运行 pybench,垃圾回收花费的时间看起来并不大。显而易见,大量分配容器对象的应用会引起更多的垃圾回收时间。

目前的补丁增加了一个新的配置项来激活垃圾回收器。有垃圾回收器的 Python 与标准 Python 是二进制兼容的。如果这个选项是关闭的,对 Python 解释器的工作就没有影响。

我该怎么使用它?

只要下载目前版本的 Python 就可以了。垃圾回收器已经包括在了 2.0 以后的版本中,并且默认是默认开启的。如果你在用 Python 1.5.2 版,这里有一个也许能工作的老版本的补丁。如果你用的是 Windows 平台,你可以下载一个用来替代的 python15.dll。

Boehm-Demers 保守垃圾回收

这个补丁增加了一些修改到 Python 1.5.2,以使用 Boehm-Demers 保守垃圾回收。但是你必须先打上这个补丁。依然是采用了引用计数。垃圾回收器只释放引用计数没有释放的内存(即循环引用)。这样应该性能最好。你需要:

$ cd Python-1.5.2
$ patch -p1 < ../gc-malloc-cleanup.diff
$ patch -p1 < ../gc-boehm.diff
$ autoconf
$ ./configure --with-gc

这个补丁假设你安装了 libgc.a,使得 -lgc 链接选项可用(/usr/local/lib 也应该可以)。如果你没有这个库,在编译以前下载安装。

目前,这个补丁只在 Linux 上测试过。在其 他Unix 机器上也许也会工作。在我的 Linux 机器上,GC 版本的 Python 通过了所有的回归测试。

Python内存管理方式和垃圾回收算法解析相关推荐

  1. python终结一个循环额_Python语言入门之内存管理方式和垃圾回收算法解析

    本文主要向大家介绍了Python语言入门之内存管理方式和垃圾回收算法解析,通过具体的内容向大家展示,希望对大家学习Python语言有所帮助. 在列表,元组,实例,类,字典和函数中存在循环引用问题.有 ...

  2. JVM内存管理机制和垃圾回收机制

    JVM内存管理机制和垃圾回收机制 JVM结构 图片描述: java源码编译成class文件 class文件通过类加载器加载到内存 其中方法区存放的是运行时的常量.静态变量.类信息等,被所有线程共享 堆 ...

  3. 深度解析Python的内存管理机制:垃圾回收机制

    Python程序在运行时,需要在内存中开辟出一块空间,用于存放运行时产生的临时变量,计算完成后,再将结果输出到永久性存储器中.但是当数据量过大,或者内存空间管理不善,就很容易出现内存溢出的情况,程序可 ...

  4. python内存管理机制错误_Python内存管理机制和垃圾回收机制的简单理解

    一.内存管理机制 1.由c开发出来的cpython 2.include / objests 3.需要下载python源码包 4.Pyobject:float PyVarObject: 5.在pytho ...

  5. jvm对象从新生代到老年代_JVM内存管理、JVM垃圾回收机制、新生代、老年代以及永久代...

    内存模型 JVM运行时数据区由程序计数器.堆.虚拟机栈.本地方法栈.方法区部分组成,结构图如下所示. JVM内存结构由程序计数器.堆.栈.本地方法栈.方法区等部分组成,结构图如下所示: 1)程序计数器 ...

  6. PHP内存管理机制与垃圾回收机制

    转载:https://www.cnblogs.com/zk0533/p/5667122.html PHP内存管理机制 var_dump(memory_get_usage()); //获取内存 $a = ...

  7. 垃圾回收概述(垃圾回收算法)

    垃圾回收概述 Java 和 C++语言的区别,就在于垃圾收集技术和内存动态分配上,C++语言没有垃圾收集技术,需要程序员手动的收集. 垃圾收集,不是Java语言的伴生产物.早在1960年,第一门开始使 ...

  8. 垃圾回收相关知识(垃圾回收算法及垃圾收集器)

    文章目录 前言 一.如何判断对象已死/是垃圾? 1. 引用计数算法 2. 可达性分析算法 二.垃圾回收算法 1. 标记-清除算法 2. 复制算法 3. 标记-整理算法 4. 分代收集算法 三.垃圾收集 ...

  9. python内存管理和释放_《python解释器源码剖析》第17章--python的内存管理与垃圾回收...

    17.0 序 内存管理,对于python这样的动态语言是至关重要的一部分,它在很大程度上决定了python的执行效率,因为在python的运行中会创建和销毁大量的对象,这些都设计内存的管理.同理pyt ...

最新文章

  1. Boost:加载评估WebSocket服务器的性能测试程序
  2. python文件处理seek_python文件操作 seek(),tell()
  3. 从零单排HBase 02:全面认识HBase架构(建议收藏)
  4. 关于mmdetection上手的几点说明
  5. RedHat 7配置keepalived+LVS实现高可用的Web负载均衡
  6. 【人工智能沙龙】未来,语音识别可能应用于哪些商业化场景?
  7. Java隔离容器之sofa-ark使用说明及源码解析
  8. php7isapi,WINDOWS 2000下使用ISAPI方式安装PHP
  9. 固定ip计算机现实无法识别的网络,电脑显示无法识别网络怎么办
  10. 【cocos】Sprite九宫格拉伸SLICED
  11. 百行代码手撸扫雷(下)c/c++
  12. 新华三交换机密码重置
  13. a4如何打印双面小册子_用word如何打印小册子(一张A4打两张那种的)
  14. 不小心把苹果手机忘记密码锁屏了怎么办
  15. 理解误区——mysql中tinyint与Java的数据类型的对应关系;tinyint(1) 与tinyint(4)的区别
  16. 自动化测试框架-数据驱动(1)
  17. Linux上查看内存个数和硬盘个数
  18. 使用MATLAB来可视化三维点云上的法向量
  19. Assignment 3
  20. 【数据库课设】图书馆借阅系统

热门文章

  1. 微信开发接口调用(前端+.net服务端)
  2. 凄怆与悲凉(灾区现场最新照片)
  3. 上班族的10大经典哲学,还有什么能难倒你?[轉自太平洋電腦網]
  4. hp designiet 500绘图仪程序_邹军:通过数控宏程序实现刀具寿命管理
  5. C语言程序练习- L1-040 最佳情侣身高差 (10分)
  6. 超级详细AST抽象语法树Javascript
  7. linux怎么用两个进程传值,关于linux:将变量脚本参数传递给另一个脚本,然后将qsub传递给程序...
  8. java for each 的源_Java JDK1.5的新特性之for-each循环的底层原理
  9. 后缀数组求最长重复子串
  10. 《dinv into python》开始了解python