前言
GC垃圾回收在python中是很重要的一部分,同样我将分两次去讲解Garbage collection垃圾回收,此篇为Garbage collection垃圾回收第一篇,下面开始今天的说明~~~
1.Garbage collection(GC垃圾回收)
现在的⾼级语⾔如java,c#等,都采⽤了垃圾收集机制,⽽不再是c,c++⾥ ⽤户⾃⼰管理维护内存的⽅式。⾃⼰管理内存极其⾃由,可以任意申请内存,但如同⼀把双刃剑,为⼤量内存泄露,悬空指针等bug埋下隐患。 对于⼀个字符串、列表、类甚⾄数值都是对象,且定位简单易⽤的语⾔,⾃然不会让⽤户去处理如何分配回收内存的问题。 python⾥也同java⼀样采⽤了垃圾收集机制,不过不⼀样的是: python采⽤的是引⽤计数机制为主,标记-清除和分代收集两种机制为辅的策略。
引⽤计数机制:
python⾥每⼀个东⻄都是对象,它们的核⼼就是⼀个结构体: PyObject
typedef struct_object{ int ob_refcnt; struct_typeobject *ob_type; }PyObject;
PyObject是每个对象必有的内容,其中ob_refcnt就是做为引⽤计数。当⼀个对象有新的引⽤时,它的ob_refcnt就会增加,当引⽤它的对象被删除,它的 ob_refcnt就会减少.
#define Py_INCREF(op) ((op)->ob_refcnt++) //增加计数 #defien Py_DECREF(op) \ //减少计数 if(--(op)->ob_refcnt!=0)\ ;\ else \ _Py_Dealloc((PyObject *)(op))
当引⽤计数为0时,该对象⽣命就结束了。
引⽤计数机制的优点:
- 简单
- 实时性:⼀旦没有引⽤,内存就直接释放了。不⽤像其他机制等到特定时机。实时性还带来⼀个好处:处理回收内存的时间分摊到了平时。
引⽤计数机制的缺点:
- 维护引⽤计数消耗资源
- 循环引⽤
list1 = [] list2 = [] list1.append(list2) list2.append(list1)
list1与list2相互引⽤,如果不存在其他对象对它们的引⽤,list1与list2的引⽤ 计数也仍然为1,所占⽤的内存永远⽆法被回收,这将是致命的。 对于如今的强⼤硬件,缺点1尚可接受,但是循环引⽤导致内存泄露,注定python还将 引⼊新的回收机制。(标记清除和分代收集)
2.画说Ruby与Python垃圾回收
英⽂原⽂: visualizing garbage collection in ruby and python
2.1.应⽤程序那颗跃动的⼼
GC系统所承担的⼯作远⽐"垃圾回收"多得多。实际上,它们负责三个重要任务:
- 为新⽣成的对象分配内存
- 识别那些垃圾对象
- 从垃圾对象那回收内存
如果将应⽤程序⽐作⼈的身体:所有你所写的那些优雅的代码,业务逻辑, 算法,应该就是⼤脑。以此类推,垃圾回收机制应该是那个身体器官呢?(我从RuPy听众那听到了不少有趣的答案:腰⼦、⽩⾎球)
我认为垃圾回收就是应⽤程序那颗跃动的⼼。像⼼脏为身体其他器官提供⾎ 液和营养物那样,垃圾回收器为你的应该程序提供内存和对象。如果⼼脏停跳,过不了⼏秒钟⼈就完了。如果垃圾回收器停⽌⼯作或运⾏迟缓,像动脉阻塞,你的应⽤程序效率也会下降,直⾄最终死掉。
2.2.一个简单的例子
运⽤实例⼀贯有助于理论的理解。下⾯是⼀个简单类,分别⽤Python和Ruby写成,我们今天就以此为例:
顺便提⼀句,两种语⾔的代码竟能如此相像:Ruby和Python在表达同⼀事物上真的只是略有不同。但是在这两种语⾔的内部实现上是否也如此相似呢?
2.3.Ruby的对象分配
当我们执⾏上⾯的Node.new(1)时,Ruby到底做了什么?Ruby是如何为我们 创建新的对象的呢? 出乎意料的是它做的⾮常少。实际上,早在代码开始执⾏前,Ruby就提前创建了成百上千个对象,并把它们串在链表上,名⽈:可⽤列表。下图所示为可⽤列表的概念图:
想象⼀下每个⽩⾊⽅格上都标着⼀个"未使⽤预创建对象"。当我们调⽤ Node.new ,Ruby只需取⼀个预创建对象给我们使⽤即可:
上图中左侧灰格表示我们代码中使⽤的当前对象,同时其他⽩格是未使⽤对象。(请注意:⽆疑我的示意图是对实际的简化。实际上,Ruby会⽤另⼀个 对象来装载字符串"ABC",另⼀个对象装载Node类定义,还有⼀个对象装载了代码中分析出的抽象语法树,等等)
如果我们再次调⽤ Node.new,Ruby将递给我们另⼀个对象:
这个简单的⽤链表来预分配对象的算法已经发明了超过50年,⽽发明⼈这是 赫赫有名的计算机科学家John McCarthy,⼀开始是⽤Lisp实现的。Lisp不仅 是最早的函数式编程语⾔,在计算机科学领域也有许多创举。其⼀就是利⽤垃圾回收机制⾃动化进⾏程序内存管理的概念。
标准版的Ruby,也就是众所周知的"Matz's Ruby Interpreter"(MRI),所使⽤的 GC算法与McCarthy在1960年的实现⽅式很类似。⽆论好坏,Ruby的垃圾回 收机制已经53岁⾼龄了。像Lisp⼀样,Ruby预先创建⼀些对象,然后在你分配新对象或者变量的时候供你使⽤。
2.4.Python的对象分配
我们已经了解了Ruby预先创建对象并将它们存放在可⽤列表中。那Python⼜怎么样呢?
尽管由于许多原因Python也使⽤可⽤列表(⽤来回收⼀些特定对象⽐如list), 但在为新对象和变量分配内存的⽅⾯Python和Ruby是不同的。
例如我们⽤Pyhon来创建⼀个Node对象:
与Ruby不同,当创建对象时Python⽴即向操作系统请求内存.(Python实际 上实现了⼀套⾃⼰的内存分配系统,在操作系统堆之上提供了⼀个抽象层。 但是我今天不展开说了)
当我们创建第⼆个对象的时候,再次像OS请求内存:
看起来够简单吧,在我们创建对象的时候,Python会花些时间为我们找到并分配内存。
2.5.Ruby开发者住在凌乱的房间里
Ruby把⽆⽤的对象留在内存⾥,直到下⼀次GC执⾏
回过来看Ruby。随着我们创建越来越多的对象,Ruby会持续寻可⽤列表⾥ 取预创建对象给我们。因此,可⽤列表会逐渐变短:
..然后更短:
请注意我⼀直在为变量n1赋新值,Ruby把旧值留在原处。"ABC","JKL"和"MNO"三个Node实例还滞留在内存中。Ruby不会⽴即清除代码中不再使⽤的旧对象!Ruby开发者们就像是住在⼀间凌乱的房间,地板上摞着⾐服,要么洗碗池⾥都是脏盘⼦。作为⼀个Ruby程序员,⽆⽤的垃圾对象会⼀直环绕着你。
2.6.Python开发者住在卫生之家庭
⽤完的垃圾对象会⽴即被Python打扫⼲净
Python与Ruby的垃圾回收机制颇为不同。让我们回到前⾯提到的三个Python Node对象:
在内部,创建⼀个对象时,Python总是在对象的C结构体⾥保存⼀个整数, 称为引⽤数 。期初,Python将这个值设置为1:
值为1说明分别有个⼀个指针指向或是引⽤这三个对象。假如我们现在创建⼀个新的Node实例,JKL:
与之前⼀样,Python设置JKL的引⽤数为1。然⽽,请注意由于我们改变了n1 指向了JKL,不再指向ABC,Python就把ABC的引⽤数置为0了。 此刻, Python垃圾回收器⽴刻挺身⽽出!每当对象的引⽤数减为0,Python⽴即将其释放,把内存还给操作系统:
上⾯Python回收了ABC Node实例使⽤的内存。记住,Ruby弃旧对象原地于不顾,也不释放它们的内存。
Python的这种垃圾回收算法被称为引⽤计数。是George-Collins在1960年发明的,恰巧与John McCarthy发明的可⽤列表算法在同⼀年出现。就像MikeBernstein在6⽉份哥谭市Ruby⼤会杰出的垃圾回收机制演讲中说的: "1960年是垃圾收集器的⻩⾦年代..."。
Python开发者⼯作在卫⽣之家,你可以想象,有个患有轻度OCD(⼀种强迫症) 的室友⼀刻不停地跟在你身后打扫,你⼀放下脏碟⼦或杯⼦,有个家伙已经 准备好把它放进洗碗机了!
现在来看第⼆例⼦。加⼊我们让n2引⽤n1
上图中左边的DEF的引⽤数已经被Python减少了,垃圾回收器会⽴即回收DEF实例。同时JKL的引⽤数已经变为了2 ,因为n1和n2都指向它。