Python 内存管理与垃圾回收
1. 内存管理概述
1.1 手动内存管理
在计算机发展的早期,编程语言提供了手动内存管理的机制,例如 C 语言,提供了用于分配和释放的函数 malloc 和 free,如下所示:
#include <stdlib.h>void *malloc(size_t size);void free(void *p);
函数 malloc 分配指定大小 size 的内存,返回内存的首地址
函数 free 释放之前申请的内存
程序员负责保证内存管理的正确性:使用 malloc 申请一块内存后,如果不再使用,需要使用 free 将其释放,示例如下:
#include <stdlib.h>void test(){void *p = malloc();访问 p 指向的内存区域;free(p);}int main(){test();}
使用 malloc(10) 分配一块大小为 10 个字节的内存区域
使用 free§ 释放这块内存区域
如果忘记释放之前使用 malloc 申请的内存,则会导致可用内存不断减少,这种现象被称为 “内存泄漏”,示例如下:
#include <stdio.h>#include <stdlib.h>void test(){void *p = malloc();访问 p 指向的内存区域;}int main(){while ()test();}
在函数 test 中,使用 malloc 申请一块内存
但是使用完毕后,忘记释放了这块内存
在函数 main 中,循环调用函数 test()
每次调用函数 test(),都会造成内存泄漏
最终,会耗尽所有的内存
1.2 自动内存管理
在计算机发展的早期,硬件性能很差,为了最大程度的压榨硬件性能,编程语言提供了手动管理内存的机制。手动管理内存的机制的优点在于能够有效规划和利用内存,其缺点在于太繁琐了,很容易出错。
随着计算机的发展,硬件性能不断提高,这时候出现的编程语言,例如:Java、C#、PHP、Python,则提供了自动管理内存的机制:程序员申请内存后,不需要再显式的释放内存,由编程语言的解释器负责释放内存,从根本上杜绝了 “内存泄漏” 这类错误。
在下面的 Python 程序中,在无限循环中不断的申请内存:
class Person:def __init__(self, name, age):self.name = name self.age = agewhile True:person = Person('tom', )
类 Person 包含两个属性:name 和 age
在 while 循环中,使用类 Person 生成一个实例 person
需要申请一块内存用于保存实例 person 的属性
Python 解释器运行这个程序时,发现实例 person 不再被引用后,会自动的释放 person 占用的空间。因此这个程序可以永远的运行下去,而不会把内存耗尽。
2. 基于引用计数的内存管理
2.1 基本原理
引用计数是一种最简单的自动内存管理机制:
每个对象都有一个引用计数
当把该对象赋值给一个变量时,对象的引用计数递增 1
引用计数的实例如下:
A = object()B = A A = NoneB = None
在第 1 行,使用 object() 创建一个对象,变量 A 指向该对象
对象的引用计数变化为 1
在第 2 行,变量 B 指向相同的对象
对象的引用计数变化为 2
在第 3 行,变量 A 指向 None
对象的引用计数变化为 1
在第 3 行,变量 B 指向 None
对象的引用计数变化为 0
从图中可以看出,当变量 A 和变量 B 都不再指向对象时,对象的引用计数变为 0,系统检测到该对象成为废弃对象,可以将此废弃对象回收。
2.2 优点和缺点
引用计数的优点在于:
实现简单
系统检测到对象的引用计数变为 0 后,可以及时的释放废弃的对象
处理回收内存的时间分摊到了平时
引用计数的缺点在于:
维护引用计数消耗性能,每次变量赋值时,都需要维护维护引用计数
无法释放存在循环引用的对象
下面是一个存在循环引用的例子:
class Node:def __init__(self, data, next):self.data = data self.next = nextnode = Node(, None)node.next = node node = None
在第 6 行,创建对象 node
对象 node 的 next 指向 None
此时对象 node 的引用计数为 1
在第 7 行,对象 node 的 next 指向 node 自身
此时对象 node 的引用计数为 2
在第 7 行,对象 node 指向 None
此时对象 node 的引用计数为 1
对象 node 的 next 字段指向自身,导致:即使没有外部的变量指向对象 node,对象 node 的引用计数也不会变为 0,因此对象 node 就永远不会被释放了。
3. 基于垃圾回收的内存管理
3.1 基本原理
垃圾回收是目前主流的内存管理机制:
通过一系列的称为 “GC Root” 的对象作为起始对象
从 GC Root 出发,进行遍历
最终将对象划分为两类:
从 GC Root 可以到达的对象
从 GC Root 无法到达的对象
从 GC Root 无法到达的对象被认为是废弃对象,可以被系统回收。
在 Python 语言中,可作为 GC Roots 的对象主要是指全局变量指向的对象。
从 GC Roots 出发,可以到达 object 1、object 2、object 3、object 4
从 GC Roots 出发,无法到达 object 5、object 6、object 7,它们被判定为可回收的对象
3.2 优点和缺点
垃圾回收的优点在于:
可以处理存在循环引用的对象
垃圾回收的缺点在于:
实现复杂
进行垃圾回收时,需要扫描程序中所有的对象,因此需要暂停程序的运行。当程序中对象数量较多时,暂停程序的运行时间过长,系统会有明显的卡顿现象。
4. Python 的内存管理机制
Python 的内存管理采用了混合的方法:
Python 使用引用计数来保持追踪内存中的对象,当对象的引用计数为 0 时,回收该对象
Python 同时使用垃圾回收机制来回收存在有循环引用的对象
下面的例子中,演示了 Python 的内存管理策略:
class Circular:def __init__(self):self.data = self.next = selfclass NonCircular:def __init__(self):self.data = self.next = Nonedef hybrid():while True:circular = Circular()nonCircular = NonCircular()hybrid()
类 Circular,创建了一个包含循环引用的对象
self.next 指向自身,导致了循环引用
类 Circular 的实例只能被垃圾回收机制释放
类 NonCircular,创建了一个不包含循环引用的对象
self.next 指向 None,没有循环引用
类 NonCircular 的实例可以引用计数机制释放
在方法 hybrid 中
在无限循环中,不断的申请 Circular 实例和 NonCircular 实例
通过引用计数和垃圾回收机制,内存不会被耗尽,程序可以永远的运行下去。